13
0
livetrax/libs/surfaces/launchkey_4/launchkey_4.cc
Mads Kiilerich 140670541f Use .inc.h for #include files that not are plain headers
Regular .h files *should* be self-contained and independent of previous
includes and guarded to only include once. Make it clear which files
that *doesn't* apply for at all.
2024-10-20 03:11:53 +02:00

2716 lines
56 KiB
C++

/*
* Copyright (C) 2016-2018 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2017-2018 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <algorithm>
#include <bitset>
#include <cmath>
#include <limits>
#include <regex>
#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/dB.h"
#include "ardour/debug.h"
#include "ardour/internal_send.h"
#include "ardour/midiport_manager.h"
#include "ardour/midi_track.h"
#include "ardour/midi_port.h"
#include "ardour/plugin.h"
#include "ardour/plugin_insert.h"
#include "ardour/selection.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/triggerbox.h"
#include "ardour/types_convert.h"
#include "ardour/utils.h"
#include "gtkmm2ext/gui_thread.h"
#include "gtkmm2ext/rgb_macros.h"
#include "gtkmm2ext/colors.h"
#include "gui.h"
#include "launchkey_4.h"
#include "pbd/i18n.h"
#ifdef PLATFORM_WINDOWS
#define random() rand()
#endif
using namespace ARDOUR;
using namespace PBD;
using namespace Glib;
using namespace ArdourSurface;
using namespace ArdourSurface::LAUNCHPAD_NAMESPACE;
using namespace Gtkmm2ext;
#include "pbd/abstract_ui.inc.cc" // instantiate template
/* USB IDs */
#define NOVATION 0x1235
#define LAUNCHKEY4_MINI_25 0x0141
#define LAUNCHKEY4_MINI_37 0x0142
#define LAUNCHKEY4_25 0x0143
#define LAUNCHKEY4_37 0x0144
#define LAUNCHKEY4_49 0x0145
#define LAUNCHKEY4_61 0x0146
static int first_fader = 0x9;
static const int PAD_COLUMNS = 8;
static const int PAD_ROWS = 2;
static const int NFADERS = 9;
static int last_detected = 0x0;
bool
LaunchKey4::available ()
{
/* no preconditions other than the device being present */
return true;
}
bool
LaunchKey4::match_usb (uint16_t vendor, uint16_t device)
{
if (vendor != NOVATION) {
return false;
}
switch (device) {
case LAUNCHKEY4_MINI_25:
case LAUNCHKEY4_MINI_37:
case LAUNCHKEY4_25:
case LAUNCHKEY4_37:
case LAUNCHKEY4_49:
case LAUNCHKEY4_61:
last_detected = device;
return true;
}
return false;
}
bool
LaunchKey4::probe (std::string& i, std::string& o)
{
vector<string> midi_inputs;
vector<string> midi_outputs;
AudioEngine::instance()->get_ports ("", DataType::MIDI, PortFlags (IsOutput|IsTerminal), midi_inputs);
AudioEngine::instance()->get_ports("", DataType::MIDI, PortFlags(IsInput | IsTerminal), midi_outputs);
if (midi_inputs.empty() || midi_outputs.empty()) {
return false;
}
std::regex rx (X_("Launchkey (Mini MK4|MK4).*MI"), std::regex::extended);
auto has_lppro = [&rx](string const &s) {
std::string pn = AudioEngine::instance()->get_hardware_port_name_by_name(s);
return std::regex_search (pn, rx);
};
auto pi = std::find_if (midi_inputs.begin(), midi_inputs.end(), has_lppro);
auto po = std::find_if (midi_outputs.begin (), midi_outputs.end (), has_lppro);
if (pi == midi_inputs.end () || po == midi_outputs.end ()) {
return false;
}
i = *pi;
o = *po;
return true;
}
LaunchKey4::LaunchKey4 (ARDOUR::Session& s)
#ifdef LAUNCHPAD_MINI
: MIDISurface (s, X_("Novation Launchkey Mini"), X_("Launchkey Mini"), true)
#else
: MIDISurface (s, X_("Novation Launchkey 4"), X_("Launchkey MK4"), true)
#endif
, _daw_out_port (nullptr)
, _gui (nullptr)
, scroll_x_offset (0)
, scroll_y_offset (0)
, device_pid (0x0)
, mode_channel (0xf)
, pad_function (MuteSolo)
, shift_pressed (false)
, layer_pressed (false)
, bank_start (0)
, button_mode (ButtonsRecEnable) // reset via toggle later
, encoder_mode (EncoderMixer)
, num_plugin_controls (0)
{
run_event_loop ();
port_setup ();
std::string pn_in, pn_out;
if (probe (pn_in, pn_out)) {
_async_in->connect (pn_in);
_async_out->connect (pn_out);
}
build_color_map ();
build_pad_map ();
Trigger::TriggerPropertyChange.connect (trigger_connections, invalidator (*this), std::bind (&LaunchKey4::trigger_property_change, this, _1, _2), this);
ControlProtocol::PluginSelected.connect (session_connections, invalidator (*this), std::bind (&LaunchKey4::plugin_selected, this, _1), this);
session->RecordStateChanged.connect (session_connections, invalidator(*this), std::bind (&LaunchKey4::record_state_changed, this), this);
session->TransportStateChange.connect (session_connections, invalidator(*this), std::bind (&LaunchKey4::transport_state_changed, this), this);
session->RouteAdded.connect (session_connections, invalidator(*this), std::bind (&LaunchKey4::stripables_added, this), this);
session->SoloChanged.connect (session_connections, invalidator(*this), std::bind (&LaunchKey4::solo_changed, this), this);
}
LaunchKey4::~LaunchKey4 ()
{
DEBUG_TRACE (DEBUG::Launchkey, "launchkey control surface object being destroyed\n");
trigger_connections.drop_connections ();
route_connections.drop_connections ();
session_connections.drop_connections ();
for (size_t n = 0; n < sizeof (pads) / sizeof (pads[0]); ++n) {
pads[n].timeout_connection.disconnect ();
}
stop_event_loop ();
tear_down_gui ();
MIDISurface::drop ();
}
void
LaunchKey4::transport_state_changed ()
{
MIDI::byte msg[9];
msg[0] = 0xb0 | mode_channel;
msg[1] = 0x73;
msg[3] = 0xb0 | mode_channel;
msg[4] = Play;
msg[6] = 0xb0 | mode_channel;
msg[7] = Stop;
if (session->transport_rolling()) {
msg[2] = 0x7f;
msg[5] = 0x0;
} else {
msg[2] = 0x0;
msg[5] = 0x7f;
}
if (session->get_play_loop()) {
msg[8] = 0x7f;
} else {
msg[8] = 0x0;
}
daw_write (msg, 9);
map_rec_enable ();
}
void
LaunchKey4::record_state_changed ()
{
map_rec_enable();
}
void
LaunchKey4::map_rec_enable ()
{
if (button_mode != ButtonsRecEnable) {
return;
}
MIDI::byte msg[3];
int channel = session->actively_recording() ? 0x0 : 0x2;
const int rec_color_index = 0x5; /* bright red */
const int norec_color_index = 0x0;
/* The global rec-enable button */
msg[0] = 0xb0 | channel;
msg[1] = 0x75;
msg[2] = session->get_record_enabled() ? rec_color_index : norec_color_index;
daw_write (msg, 3);
/* Now all the tracks */
for (int i = 0; i < NFADERS-1; ++i) {
show_rec_enable (i);
}
}
void
LaunchKey4::show_rec_enable (int n)
{
LightingMode mode = session->actively_recording() ? Solid : Pulse;
const int rec_color_index = 0x5; /* bright red */
const int norec_color_index = 0x0;
if (stripable[n]) {
std::shared_ptr<AutomationControl> ac = stripable[n]->rec_enable_control();
if (ac) {
light_button (Button1 + n, mode, ac->get_value() ? rec_color_index : norec_color_index);
} else {
light_button (Button1 + n, Solid, 0x0);
}
} else {
light_button (Button1 + n, Solid, 0x0);
}
}
int
LaunchKey4::set_active (bool yn)
{
DEBUG_TRACE (DEBUG::Launchkey, string_compose("Launchpad X::set_active init with yn: %1\n", yn));
if (yn == active()) {
return 0;
}
if (yn) {
if (device_acquire ()) {
return -1;
}
} else {
/* Control Protocol Manager never calls us with false, but
* insteads destroys us.
*/
}
ControlProtocol::set_active (yn);
DEBUG_TRACE (DEBUG::Launchkey, string_compose("Launchpad X::set_active done with yn: '%1'\n", yn));
return 0;
}
void
LaunchKey4::run_event_loop ()
{
DEBUG_TRACE (DEBUG::Launchkey, "start event loop\n");
BaseUI::run ();
}
void
LaunchKey4::stop_event_loop ()
{
DEBUG_TRACE (DEBUG::Launchkey, "stop event loop\n");
BaseUI::quit ();
}
int
LaunchKey4::begin_using_device ()
{
DEBUG_TRACE (DEBUG::Launchkey, "begin using device\n");
/* get device model */
_data_required = true;
MidiByteArray device_inquiry (6, 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7);
write (device_inquiry);
return 0;
}
void
LaunchKey4::finish_begin_using_device ()
{
DEBUG_TRACE (DEBUG::Launchkey, "finish begin using device\n");
_data_required = false;
if (MIDISurface::begin_using_device ()) {
return;
}
connect_daw_ports ();
/* enter DAW mode */
set_daw_mode (true);
set_pad_function (MuteSolo);
/* catch current selection, if any so that we can wire up the pads if appropriate */
stripable_selection_changed ();
switch_bank (0);
toggle_button_mode ();
use_encoders (true);
set_encoder_bank (0);
/* Set configuration for fader displays, which is never altered */
MIDI::byte display_config[10];
display_config[0] = 0xf0;
display_config[1] = 0x0;
display_config[2] = 0x20;
display_config[3] = 0x29;
display_config[4] = (device_pid>>8) & 0x7f;
display_config[5] = device_pid & 0x7f;
display_config[6] = 0x4;
display_config[8] = 0x61;
display_config[9] = 0xf7;
for (int fader = 0; fader < 9; ++fader) {
/* 2 line display for all faders */
display_config[7] = 0x5 + fader;
daw_write (display_config, 10);
}
std::cerr << "Configuring displays now\n";
configure_display (StationaryDisplay, 0x1);
set_display_target (StationaryDisplay, 0, "ardour", true);
set_display_target (StationaryDisplay, 1, string(), true);
configure_display (DAWPadFunctionDisplay, 0x1);
/* In this DAW, mixer mode controls pan */
set_display_target (MixerPotMode, 1, "Level", false);
}
void
LaunchKey4::set_daw_mode (bool yn)
{
MidiByteArray msg;
msg.push_back (0x9f);
msg.push_back (0xc);
msg.push_back (yn ? 0x7f : 0x0);
daw_write (msg);
if (yn) {
mode_channel = 0x0;
} else {
mode_channel = 0xf;
}
if (yn) {
all_pads_out ();
}
}
void
LaunchKey4::all_pads (int color_index)
{
MIDI::byte msg[3];
msg[0] = 0x90;
msg[2] = color_index;
/* top row */
for (int i = 0; i < 8; ++i) {
msg[1] = 0x60 + i;
daw_write (msg, 3);
}
for (int i = 0; i < 8; ++i) {
msg[1] = 0x70 + i;
daw_write (msg, 3);
}
}
void
LaunchKey4::all_pads_out ()
{
all_pads (0x0);
}
int
LaunchKey4::stop_using_device ()
{
DEBUG_TRACE (DEBUG::Launchkey, "stop using device\n");
if (!_in_use) {
DEBUG_TRACE (DEBUG::Launchkey, "nothing to do, device not in use\n");
return 0;
}
set_daw_mode (false);
return MIDISurface::stop_using_device ();
}
XMLNode&
LaunchKey4::get_state() const
{
XMLNode& node (MIDISurface::get_state());
XMLNode* child = new XMLNode (X_("DAWInput"));
child->add_child_nocopy (_daw_in->get_state());
node.add_child_nocopy (*child);
child = new XMLNode (X_("DAWOutput"));
child->add_child_nocopy (_daw_out->get_state());
node.add_child_nocopy (*child);
return node;
}
int
LaunchKey4::set_state (const XMLNode & node, int version)
{
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("LaunchKey4::set_state: active %1\n", active()));
int retval = 0;
if (MIDISurface::set_state (node, version)) {
return -1;
}
return retval;
}
std::string
LaunchKey4::input_port_name () const
{
switch (last_detected) {
case LAUNCHKEY4_MINI_25:
case LAUNCHKEY4_MINI_37:
return X_(":Launchpad Mini MK3.*MIDI (In|2)");
default:
break;
}
return X_(":Launchpad X MK3.*MIDI (In|2)");
}
std::string
LaunchKey4::output_port_name () const
{
switch (last_detected) {
case LAUNCHKEY4_MINI_25:
case LAUNCHKEY4_MINI_37:
return X_(":Launchpad Mini MK3.*MIDI (Out|2)");
default:
break;
}
return X_(":Launchpad X MK3.*MIDI (Out|2)");
}
void
LaunchKey4::relax (Pad & pad)
{
}
void
LaunchKey4::relax (Pad & pad, int)
{
}
void
LaunchKey4::build_pad_map ()
{
for (int n = 0; n < 8; ++n) {
int pid = 0x60 + n;
pads[n] = Pad (pid, n, 0);
}
for (int n = 0; n < 8; ++n) {
int pid = 0x70 + n;
pads[8+n] = Pad (pid, n, 1);
}
}
void
LaunchKey4::use_encoders (bool onoff)
{
MIDI::byte msg[3];
msg[0] = 0xb6;
msg[1] = 0x45;
msg[2] = (onoff ? 0x7f : 0x0);
daw_write (msg, 3);
if (!onoff) {
return;
}
MIDI::byte display_config[10];
display_config[0] = 0xf0;
display_config[1] = 0x0;
display_config[2] = 0x20;
display_config[3] = 0x29;
display_config[4] = (device_pid>>8) & 0x7f;
display_config[5] = device_pid & 0x7f;
display_config[6] = 0x4;
display_config[8] = 0x62;
display_config[9] = 0xf7;
for (int encoder = 0; encoder < 8; ++encoder) {
/* 2 line display for all encoders */
display_config[7] = 0x15 + encoder;
daw_write (display_config, 10);
}
}
void
LaunchKey4::handle_midi_sysex (MIDI::Parser& parser, MIDI::byte* raw_bytes, size_t sz)
{
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::Launchkey)) {
std::stringstream str;
str << "Sysex received, size " << sz << std::endl;
str << hex;
for (size_t n = 0; n < sz; ++n) {
str << "0x" << (int) raw_bytes[n] << ' ';
}
str << std::endl;
std::cerr << str.str();
}
#endif
if (sz != 17) {
return;
}
MIDI::byte dp_lsb;
MIDI::byte dp_msb;
if (raw_bytes[1] == 0x7e &&
raw_bytes[2] == 0x0 &&
raw_bytes[3] == 0x6 &&
raw_bytes[4] == 0x2 &&
raw_bytes[5] == 0x0 &&
raw_bytes[6] == 0x20 &&
raw_bytes[7] == 0x29) {
dp_lsb = raw_bytes[8];
dp_msb = raw_bytes[9];
const int family = (dp_msb<<8)|dp_lsb;
switch (family) {
case LAUNCHKEY4_MINI_25:
case LAUNCHKEY4_MINI_37:
device_pid = 0x0213;
break;
case LAUNCHKEY4_25:
case LAUNCHKEY4_37:
case LAUNCHKEY4_49:
case LAUNCHKEY4_61:
device_pid = 0x0214;
break;
default:
return;
}
finish_begin_using_device ();
return;
}
}
void
LaunchKey4::handle_midi_controller_message_chnF (MIDI::Parser& parser, MIDI::EventTwoBytes* ev)
{
if (ev->controller_number < 0x05 || ev->controller_number > 0xd) {
return;
}
int fader_number = ev->controller_number - 0x5;
fader_move (fader_number, ev->value);
}
void
LaunchKey4::handle_midi_controller_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev)
{
/* Remember: fader controller events are delivered via if (ev->controller_::handle_midi_controller_message_chnF() */
if (&parser != _daw_in_port->parser()) {
if (ev->controller_number == 0x69 && ev->value == 0x7f) {
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("function button press on non-DAW port, CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
function_press ();
return;
}
/* we don't process CC messages from the regular port */
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("skip non-DAW CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
return;
}
#ifndef NDEBUG
std::stringstream ss;
ss << "CC 0x" << std::hex << (int) ev->controller_number << std::dec << " value (" << (int) ev->value << ")\n";
DEBUG_TRACE (DEBUG::Launchkey, ss.str());
#endif
/* Shift being pressed can change everything */
if (ev->controller_number == 0x48) {
if (ev->value) {
shift_pressed = true;
} else {
shift_pressed = false;
}
return;
}
/* Scene launch */
if (ev->controller_number == 0x68) {
if (ev->value) {
scene_press ();
}
return;
}
/* Button 9 (below fader 9 */
if (ev->controller_number == Button9) {
/* toggle on press only */
if (ev->value) {
toggle_button_mode ();
}
return;
}
/* Encoder Mode button */
if (ev->controller_number == 0x41) {
switch (ev->value) {
case 2:
set_encoder_mode (EncoderPlugins);
break;
case 1:
set_encoder_mode (EncoderMixer);
break;
case 4:
set_encoder_mode (EncoderSendA);
break;
case 5:
set_encoder_mode (EncoderTransport);
break;
default:
break;
}
return;
}
/* Encoder Bank Buttons */
if (ev->controller_number == 0x33) {
/* up'; use press only */
if (ev->value && encoder_bank > 0) {
set_encoder_bank (encoder_bank - 1);
}
return;
}
if (ev->controller_number == 0x34) {
/* down; use press only */
if (ev->value && encoder_bank < 2) {
set_encoder_bank (encoder_bank + 1);
}
return;
}
switch (ev->controller_number) {
case 0x6a:
if (ev->value) button_up ();
return;
case 0x6b:
if (ev->value) button_down ();
return;
case 0x67:
if (ev->value) button_left ();
return;
case 0x66:
if (ev->value) button_right ();
return;
}
/* Buttons below faders */
if (ev->controller_number >= Button1 && ev->controller_number <= Button8) {
if (ev->value == 0x7f) {
button_press (ev->controller_number - Button1);
} else {
button_release (ev->controller_number - Button1);
}
return;
} else if (ev->controller_number >= Knob1 && ev->controller_number <= Knob8) {
encoder (ev->controller_number - Knob1, ev->value - 64);
return;
} else if (ev->controller_number >= 0x55 && ev->controller_number <= 0x5c) {
encoder (ev->controller_number - Knob1, ev->value - 64);
return;
}
if (ev->value == 0x7f) {
switch (ev->controller_number) {
case Function:
function_press ();
break;
case Undo:
undo_press ();
break;
case Play:
if (device_pid == 0x213) {
/* Mini version only play button, so toggle */
if (session->transport_rolling()) {
transport_stop();
} else {
transport_play();
}
} else {
transport_play ();
}
break;
case Stop:
transport_stop ();
break;
case RecEnable:
set_record_enable (!get_record_enabled());
break;
case Loop:
loop_toggle ();
break;
default:
break;
}
}
}
void
LaunchKey4::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev)
{
if (ev->velocity == 0) {
handle_midi_note_off_message (parser, ev);
return;
}
if (&parser != _daw_in_port->parser()) {
/* we don't process note messages from the regular port */
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("skip non-DAW Note On %1/0x%3%4%5 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity, std::hex, (int) ev->note_number, std::dec));
return;
}
int pad_number;
switch (ev->note_number) {
case 0x60:
pad_number = 0;
break;
case 0x61:
pad_number = 1;
break;
case 0x62:
pad_number = 2;
break;
case 0x63:
pad_number = 3;
break;
case 0x64:
pad_number = 4;
break;
case 0x65:
pad_number = 5;
break;
case 0x66:
pad_number = 6;
break;
case 0x67:
pad_number = 7;
break;
case 0x70:
pad_number = 8;
break;
case 0x71:
pad_number = 9;
break;
case 0x72:
pad_number = 10;
break;
case 0x73:
pad_number = 11;
break;
case 0x74:
pad_number = 12;
break;
case 0x75:
pad_number = 13;
break;
case 0x76:
pad_number = 14;
break;
case 0x77:
pad_number = 15;
break;
default:
return;
}
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("Note On %1/0x%3%4%5 (velocity %2) => pad %6\n", (int) ev->note_number, (int) ev->velocity, std::hex, (int) ev->note_number, std::dec, pad_number));
Pad& pad = pads[pad_number];
switch (pad_function) {
case MuteSolo:
pad_mute_solo (pad);
break;
case Triggers:
pad_trigger (pad, ev->velocity);
break;
default:
break;
}
}
void
LaunchKey4::handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
{
int pad_number;
switch (ev->note_number) {
case 0x60:
pad_number = 0;
break;
case 0x61:
pad_number = 1;
break;
case 0x62:
pad_number = 2;
break;
case 0x63:
pad_number = 3;
break;
case 0x64:
pad_number = 4;
break;
case 0x65:
pad_number = 5;
break;
case 0x66:
pad_number = 6;
break;
case 0x67:
pad_number = 7;
break;
case 0x70:
pad_number = 8;
break;
case 0x71:
pad_number = 9;
break;
case 0x72:
pad_number = 10;
break;
case 0x73:
pad_number = 11;
break;
case 0x74:
pad_number = 12;
break;
case 0x75:
pad_number = 13;
break;
case 0x76:
pad_number = 14;
break;
case 0x77:
pad_number = 15;
break;
default:
return;
}
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("Note Off %1/0x%3%4%5 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity, std::hex, (int) ev->note_number, std::dec));
pad_release (pads[pad_number]);
}
void
LaunchKey4::pad_trigger (Pad& pad, int velocity)
{
if (shift_pressed) {
trigger_stop_col (pad.x, true); /* immediate */
} else {
TriggerPtr trigger = session->trigger_at (pad.x, pad.y + scroll_y_offset);
switch (trigger->state()) {
case Trigger::Stopped:
trigger->bang (velocity / 127.0f);
break;
default:
break;
}
start_press_timeout (pad);
}
}
void
LaunchKey4::pad_release (Pad& pad)
{
pad.timeout_connection.disconnect ();
}
void
LaunchKey4::start_press_timeout (Pad& pad)
{
Glib::RefPtr<Glib::TimeoutSource> timeout = Glib::TimeoutSource::create (250); // milliseconds
pad.timeout_connection = timeout->connect (sigc::bind (sigc::mem_fun (*this, &LaunchKey4::long_press_timeout), pad.x));
timeout->attach (main_loop()->get_context());
}
bool
LaunchKey4::long_press_timeout (int col)
{
std::cerr << "timeout!\n";
trigger_stop_col (col, false); /* non-immediate */
return false; /* don't get called again */
}
void
LaunchKey4::trigger_property_change (PropertyChange pc, Trigger* t)
{
if (pad_function != Triggers) {
return;
}
int x = t->box().order();
int y = t->index();
DEBUG_TRACE (DEBUG::Launchpad, string_compose ("prop change %1 for trigger at %2, %3\n", pc, x, y));
if (y < scroll_y_offset || y > scroll_y_offset + 1) {
/* not visible at present */
return;
}
if (x < scroll_x_offset || x > scroll_x_offset + 7) {
/* not visible at present */
return;
}
y -= scroll_y_offset;
x -= scroll_x_offset;
/* name property change is sent when slots are loaded or unloaded */
PropertyChange our_interests;
our_interests.add (Properties::running);
our_interests.add (Properties::name);;
if (pc.contains (our_interests)) {
Pad& pad (pads[(y*8) + x]);
std::shared_ptr<Route> r = session->get_remote_nth_route (scroll_x_offset + x);
trigger_pad_light (pad, r, t);
}
}
void
LaunchKey4::trigger_pad_light (Pad& pad, std::shared_ptr<Route> r, Trigger* t)
{
if (!r || !t || !t->playable()) {
unlight_pad (pad.id);
return;
}
MIDI::byte msg[3];
msg[0] = 0x90;
msg[1] = pad.id;
switch (t->state()) {
case Trigger::Stopped:
msg[2] = find_closest_palette_color (r->presentation_info().color());
break;
case Trigger::WaitingToStart:
msg[0] |= 0x2; /* channel 2=> pulsing */
msg[2] = 0x17; // find_closest_palette_color (r->presentation_info().color()));
break;
case Trigger::Running:
/* choose contrasting color from the base one */
msg[2] = find_closest_palette_color (HSV(r->presentation_info().color()).opposite());
break;
case Trigger::WaitingForRetrigger:
case Trigger::WaitingToStop:
case Trigger::WaitingToSwitch:
case Trigger::Stopping:
msg[0] |= 0x2; /* pulse */
msg[2] = find_closest_palette_color (HSV(r->presentation_info().color()).opposite());
}
daw_write (msg, 3);
}
void
LaunchKey4::map_triggers ()
{
for (int x = 0; x < PAD_COLUMNS; ++x) {
map_triggerbox (x);
}
}
void
LaunchKey4::map_triggerbox (int x)
{
std::shared_ptr<Route> r = session->get_remote_nth_route (x + scroll_x_offset);
for (int y = 0; y < PAD_ROWS; ++y) {
Pad& pad (pads[(y*8) + x]);
TriggerPtr t = session->trigger_at (x + scroll_x_offset, y + scroll_y_offset);
trigger_pad_light (pad, r, t.get());
}
}
void
LaunchKey4::pad_mute_solo (Pad& pad)
{
if (!stripable[pad.x]) {
return;
}
if (pad.y == 0) {
session->set_control (stripable[pad.x]->mute_control(), !stripable[pad.x]->mute_control()->get_value(), PBD::Controllable::UseGroup);
} else {
session->set_control (stripable[pad.x]->solo_control(), !stripable[pad.x]->solo_control()->get_value(), PBD::Controllable::UseGroup);
}
}
void
LaunchKey4::port_registration_handler ()
{
MIDISurface::port_registration_handler ();
connect_daw_ports ();
}
void
LaunchKey4::connect_daw_ports ()
{
if (!_daw_in || !_daw_out) {
/* ports not registered yet */
return;
}
if (_daw_in->connected() && _daw_out->connected()) {
/* don't waste cycles here */
return;
}
std::vector<std::string> midi_inputs;
std::vector<std::string> midi_outputs;
/* get all MIDI Ports */
AudioEngine::instance()->get_ports ("", DataType::MIDI, PortFlags (IsOutput|IsTerminal), midi_inputs);
AudioEngine::instance()->get_ports("", DataType::MIDI, PortFlags(IsInput | IsTerminal), midi_outputs);
if (midi_inputs.empty() || midi_outputs.empty()) {
return;
}
/* Try to find the DAW port, whose pretty name varies on Linux
* depending on the version of ALSA, but is fairly consistent across
* newer ALSA and other platforms.
*/
std::string regex_str;
if (device_pid == 0x213) {
regex_str = X_("Launchkey Mini MK4.*(DAW|MIDI 2|DA$)");
} else {
regex_str = X_("Launchkey MK4.*(DAW|MIDI 2|DA$)");
}
std::regex rx (regex_str, std::regex::extended);
auto is_dawport = [&rx](string const &s) {
std::string pn = AudioEngine::instance()->get_hardware_port_name_by_name(s);
return std::regex_search (pn, rx);
};
auto pi = std::find_if (midi_inputs.begin(), midi_inputs.end(), is_dawport);
auto po = std::find_if (midi_outputs.begin (), midi_outputs.end (), is_dawport);
if (pi == midi_inputs.end() || po == midi_inputs.end()) {
std::cerr << "daw port not found\n";
return;
}
if (!_daw_in->connected()) {
AudioEngine::instance()->connect (_daw_in->name(), *pi);
}
if (!_daw_out->connected()) {
AudioEngine::instance()->connect (_daw_out->name(), *po);
}
connect_to_port_parser (*_daw_in_port);
MIDI::Parser* p = _daw_in_port->parser();
/* fader messages are controllers but always on channel 0xf */
p->channel_controller[15].connect_same_thread (*this, std::bind (&LaunchKey4::handle_midi_controller_message_chnF, this, _1, _2));
/* Connect DAW input port to event loop */
AsyncMIDIPort* asp;
asp = dynamic_cast<AsyncMIDIPort*> (_daw_in_port);
asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &MIDISurface::midi_input_handler), _daw_in_port));
asp->xthread().attach (main_loop()->get_context());
}
int
LaunchKey4::ports_acquire ()
{
int ret = MIDISurface::ports_acquire ();
if (!ret) {
_daw_in = AudioEngine::instance()->register_input_port (DataType::MIDI, string_compose (X_("%1 daw in"), port_name_prefix), true);
if (_daw_in) {
_daw_in_port = std::dynamic_pointer_cast<AsyncMIDIPort>(_daw_in).get();
_daw_out = AudioEngine::instance()->register_output_port (DataType::MIDI, string_compose (X_("%1 daw out"), port_name_prefix), true);
}
if (_daw_out) {
_daw_out_port = std::dynamic_pointer_cast<AsyncMIDIPort>(_daw_out).get();
return 0;
}
ret = -1;
}
return ret;
}
void
LaunchKey4::ports_release ()
{
/* wait for button data to be flushed */
MIDI::Port* daw_port = std::dynamic_pointer_cast<AsyncMIDIPort>(_daw_out).get();
AsyncMIDIPort* asp;
asp = dynamic_cast<AsyncMIDIPort*> (daw_port);
asp->drain (10000, 500000);
{
Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
AudioEngine::instance()->unregister_port (_daw_in);
AudioEngine::instance()->unregister_port (_daw_out);
}
_daw_in.reset ((ARDOUR::Port*) 0);
_daw_out.reset ((ARDOUR::Port*) 0);
MIDISurface::ports_release ();
}
void
LaunchKey4::daw_write (const MidiByteArray& data)
{
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("daw write %1 %2\n", data.size(), data));
_daw_out_port->write (&data[0], data.size(), 0);
}
void
LaunchKey4::daw_write (MIDI::byte const * data, size_t size)
{
#ifndef NDEBUG
std::stringstream str;
if (DEBUG_ENABLED(DEBUG::Launchkey)) {
str << hex;
for (size_t n = 0; n < size; ++n) {
str << (int) data[n] << ' ';
}
}
#endif
DEBUG_TRACE (DEBUG::Launchkey, string_compose ("daw write %1 [%2]\n", size, str.str()));
_daw_out_port->write (data, size, 0);
}
void
LaunchKey4::stripable_selection_changed ()
{
map_selection ();
if (session->selection().first_selected_stripable()) {
set_display_target (GlobalTemporaryDisplay, 0, session->selection().first_selected_stripable()->name(), true);
}
}
void
LaunchKey4::show_scene_ids ()
{
set_display_target (DAWPadFunctionDisplay, 0, string_compose ("Scenes %1 + %2", scroll_y_offset + 1, scroll_y_offset + 2), true);
}
void
LaunchKey4::button_up ()
{
if (pad_function != Triggers) {
return;
}
if (scroll_y_offset >= 1) {
scroll_y_offset -= 1;
show_scene_ids ();
}
}
void
LaunchKey4::button_down()
{
if (pad_function != Triggers) {
return;
}
scroll_y_offset += 1;
show_scene_ids ();
}
void
LaunchKey4::build_color_map ()
{
/* RGB values taken from using color picker on PDF of LP manual, page
* 10, but without zero (off)
*/
static uint32_t novation_color_chart_left_side[] = {
0xb3b3b3ff,
0xddddddff,
0xffffffff,
0xffb3b3ff,
0xff6161ff,
0xdd6161ff,
0xb36161ff,
0xfff3d5ff,
0xffb361ff,
0xdd8c61ff,
0xb37661ff,
0xffeea1ff,
0xffff61ff,
0xdddd61ff,
0xb3b361ff,
0xddffa1ff,
0xc2ff61ff,
0xa1dd61ff,
0x81b361ff,
0xc2ffb3ff,
0x61ff61ff,
0x61dd61ff,
0x61b361ff,
0xc2ffc2ff,
0x61ff8cff,
0x61dd76ff,
0x61b36bff,
0xc2ffccff,
0x61ffccff,
0x61dda1ff,
0x61b381ff,
0xc2fff3ff,
0x61ffe9ff,
0x61ddc2ff,
0x61b396ff,
0xc2f3ffff,
0x61eeffff,
0x61c7ddff,
0x61a1b3ff,
0xc2ddffff,
0x61c7ffff,
0x61a1ddff,
0x6181b3ff,
0xa18cffff,
0x6161ffff,
0x6161ddff,
0x6161b3ff,
0xccb3ffff,
0xa161ffff,
0x8161ddff,
0x7661b3ff,
0xffb3ffff,
0xff61ffff,
0xdd61ddff,
0xb361b3ff,
0xffb3d5ff,
0xff61c2ff,
0xdd61a1ff,
0xb3618cff,
0xff7661ff,
0xe9b361ff,
0xddc261ff,
0xa1a161ff,
};
static uint32_t novation_color_chart_right_side[] = {
0x61b361ff,
0x61b38cff,
0x618cd5ff,
0x6161ffff,
0x61b3b3ff,
0x8c61f3ff,
0xccb3c2ff,
0x8c7681ff,
/**/
0xff6161ff,
0xf3ffa1ff,
0xeefc61ff,
0xccff61ff,
0x76dd61ff,
0x61ffccff,
0x61e9ffff,
0x61a1ffff,
/**/
0x8c61ffff,
0xcc61fcff,
0xcc61fcff,
0xa17661ff,
0xffa161ff,
0xddf961ff,
0xd5ff8cff,
0x61ff61ff,
/**/
0xb3ffa1ff,
0xccfcd5ff,
0xb3fff6ff,
0xcce4ffff,
0xa1c2f6ff,
0xd5c2f9ff,
0xf98cffff,
0xff61ccff,
/**/
0xff61ccff,
0xf3ee61ff,
0xe4ff61ff,
0xddcc61ff,
0xb3a161ff,
0x61ba76ff,
0x76c28cff,
0x8181a1ff,
/**/
0x818cccff,
0xccaa81ff,
0xdd6161ff,
0xf9b3a1ff,
0xf9ba76ff,
0xfff38cff,
0xe9f9a1ff,
0xd5ee76ff,
/**/
0x8181a1ff,
0xf9f9d5ff,
0xddfce4ff,
0xe9e9ffff,
0xe4d5ffff,
0xb3b3b3ff,
0xd5d5d5ff,
0xf9ffffff,
/**/
0xe96161ff,
0xe96161ff,
0x81f661ff,
0x61b361ff,
0xf3ee61ff,
0xb3a161ff,
0xeec261ff,
0xc27661ff
};
for (size_t n = 0; n < sizeof (novation_color_chart_left_side) / sizeof (novation_color_chart_left_side[0]); ++n) {
uint32_t color = novation_color_chart_left_side[n];
/* Add 1 to account for missing zero at zero in the table */
std::pair<int,uint32_t> p (1 + n, color);
color_map.insert (p);
}
for (size_t n = 0; n < sizeof (novation_color_chart_right_side) / sizeof (novation_color_chart_right_side[0]); ++n) {
uint32_t color = novation_color_chart_right_side[n];
/* Add 40 to account for start offset number shown in page 10 of the LP manual */
std::pair<int,uint32_t> p (40 + n, color);
color_map.insert (p);
}
}
int
LaunchKey4::find_closest_palette_color (uint32_t color)
{
auto distance = std::numeric_limits<double>::max();
int index = -1;
NearestMap::iterator n = nearest_map.find (color);
if (n != nearest_map.end()) {
return n->second;
}
HSV hsv_c (color);
for (auto const & c : color_map) {
HSV hsv_p (c.second);
double chr = M_PI * (hsv_c.h / 180.0);
double phr = M_PI * (hsv_p.h /180.0);
double t1 = (sin (chr) * hsv_c.s * hsv_c.v) - (sin (phr) * hsv_p.s* hsv_p.v);
double t2 = (cos (chr) * hsv_c.s * hsv_c.v) - (cos (phr) * hsv_p.s * hsv_p.v);
double t3 = hsv_c.v - hsv_p.v;
double d = (t1 * t1) + (t2 * t2) + (0.5 * (t3 * t3));
if (d < distance) {
index = c.first;
distance = d;
}
}
nearest_map.insert (std::pair<uint32_t,int> (color, index));
return index;
}
void
LaunchKey4::route_property_change (PropertyChange const & pc, int col)
{
if (pc.contains (Properties::color)) {
map_triggerbox (col);
}
if (pc.contains (Properties::selected)) {
}
}
void
LaunchKey4::fader_move (int which, int val)
{
std::shared_ptr<AutomationControl> ac;
if (which == 8) {
std::shared_ptr<Route> monitor = session->monitor_out();
if (monitor) {
ac = monitor->gain_control();
} else {
std::shared_ptr<Route> master = session->master_out();
if (!master) {
return;
}
ac = master->gain_control();
}
} else {
if (!stripable[which]) {
return;
}
ac = stripable[which]->gain_control();
}
if (ac) {
gain_t gain = ARDOUR::slider_position_to_gain_with_max (val/127.0, ARDOUR::Config->get_max_gain());
session->set_control (ac, gain, PBD::Controllable::NoGroup);
char buf[16];
snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (gain));
set_display_target (DisplayTarget (0x5 + which), 1, buf, true);
}
}
void
LaunchKey4::automation_control_change (int n, std::weak_ptr<AutomationControl> wac)
{
std::shared_ptr<AutomationControl> ac = wac.lock();
if (!ac) {
return;
}
MIDI::byte msg[3];
msg[0] = 0xb4;
msg[1] = first_fader + n;
switch (current_fader_bank) {
case VolumeFaders:
case SendAFaders:
case SendBFaders:
msg[2] = (MIDI::byte) (ARDOUR::gain_to_slider_position_with_max (ac->get_value(), ARDOUR::Config->get_max_gain()) * 127.0);
break;
case PanFaders:
msg[2] = (MIDI::byte) (ac->get_value() * 127.0);
break;
default:
break;
}
daw_write (msg, 3);
}
void
LaunchKey4::encoder (int which, int step)
{
switch (encoder_mode) {
case EncoderPlugins:
encoder_plugin (which, step);
break;
case EncoderMixer:
encoder_mixer (which, step);
break;
case EncoderSendA:
encoder_senda (which, step);
break;
case EncoderTransport:
encoder_transport (which, step);
break;
}
}
void
LaunchKey4::plugin_selected (std::weak_ptr<PluginInsert> wpi)
{
std::shared_ptr<PluginInsert> pi (wpi.lock());
if (!pi) {
return;
}
current_plugin = pi->plugin();
uint32_t n = 0;
while (n < 24) {
Evoral::ParameterDescriptor pd;
Evoral::Parameter param (PluginAutomation, 0, n);
std::shared_ptr<AutomationControl> ac = pi->automation_control (param, false);
if (ac) {
controls[n] = ac;
} else {
break;
}
++n;
}
num_plugin_controls = n;
while (n < 24) {
controls[n].reset ();
++n;
}
if (encoder_mode == EncoderPlugins) {
label_encoders ();
/* light up/down arrows appropriately */
set_encoder_bank (encoder_bank);
}
}
void
LaunchKey4::show_encoder_value (int n, std::shared_ptr<Plugin> plugin, int control, std::shared_ptr<AutomationControl> ac, bool display)
{
bool ok;
std::string str;
uint32_t p = plugin->nth_parameter (control, ok);
if (!ok || !plugin->print_parameter (p, str)) {
char buf[32];
double val = ac->get_value ();
snprintf (buf, sizeof (buf), "%.2f", val);
set_display_target (DisplayTarget (0x15 + n), 2, buf, display);
return;
}
set_display_target (DisplayTarget (0x15 + n), 2, str, true);
}
void
LaunchKey4::setup_screen_for_encoder_plugins ()
{
uint32_t n = 0;
std::shared_ptr<ARDOUR::Plugin> plugin = current_plugin.lock();
if (plugin) {
while (n < 8) {
uint32_t ctrl = (encoder_bank * 8) + n;
std::shared_ptr<AutomationControl> ac = controls[ctrl].lock();
bool ok;
if (!ac) {
break;
}
int p = plugin->nth_parameter (n, ok);
if (!ok) {
break;
}
std::string label = plugin->parameter_label (p);
set_display_target (DisplayTarget (0x15+n), 0, plugin->name(), (n == 0));
set_display_target (DisplayTarget (0x15+n), 1, label,(n == 0));
show_encoder_value (n, plugin, ctrl, ac, (n == 0));
++n;
}
}
while (n < 8) {
set_display_target (DisplayTarget (0x15+n), 0, plugin->name(), (n == 0));
set_display_target (DisplayTarget (0x15+n), 1, "--", (n == 0));
set_display_target (DisplayTarget (0x15+n), 2, string(), (n == 0));
++n;
}
}
void
LaunchKey4::encoder_plugin (int which, int step)
{
std::shared_ptr<Plugin> plugin (current_plugin.lock());
if (!plugin) {
return;
}
int control = which + (encoder_bank * 8);
std::shared_ptr<AutomationControl> ac (controls[control].lock());
if (!ac) {
return;
}
double val = ac->internal_to_interface (ac->get_value());
val += step/127.0;
ac->set_value (ac->interface_to_internal (val), PBD::Controllable::NoGroup);
show_encoder_value (which, plugin, control, ac, true);
}
void
LaunchKey4::encoder_mixer (int which, int step)
{
switch (encoder_bank) {
case 0:
encoder_level (which, step);
break;
case 1:
encoder_pan (which, step);
break;
default:
break;
}
}
void
LaunchKey4::encoder_pan (int which, int step)
{
if (!stripable[which]) {
return;
}
std::shared_ptr<AutomationControl> ac (stripable[which]->pan_azimuth_control());
if (!ac) {
return;
}
double val = ac->internal_to_interface (ac->get_value());
session->set_control (ac, ac->interface_to_internal (val - (step/127.0)), Controllable::NoGroup);
char buf[64];
snprintf (buf, sizeof (buf), _("L:%3d R:%3d"), (int) rint (100.0 * (1.0 - val)), (int) rint (100.0 * val));
set_display_target (DisplayTarget (0x15 + which), 2, buf, true);
}
void
LaunchKey4::encoder_level (int which, int step)
{
if (!stripable[which]) {
return;
}
std::shared_ptr<GainControl> gc (stripable[which]->gain_control());
if (!gc) {
return;
}
gain_t gain;
if (shift_pressed) {
gain = gc->get_value();
} else {
double pos = ARDOUR::gain_to_slider_position_with_max (gc->get_value(), ARDOUR::Config->get_max_gain());
pos += (step/127.0);
gain = ARDOUR::slider_position_to_gain_with_max (pos, ARDOUR::Config->get_max_gain());
session->set_control (gc, gain, Controllable::NoGroup);
}
char buf[16];
snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (gain));
set_display_target (DisplayTarget (0x15 + which), 2, buf, true);
}
void
LaunchKey4::encoder_senda (int which, int step)
{
std::shared_ptr<Stripable> s = session->selection().first_selected_stripable();
if (!s) {
return;
}
std::shared_ptr<Route> target_bus = std::dynamic_pointer_cast<Route> (s);
if (!target_bus) {
return;
}
if (!stripable[which]) {
return;
}
std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (stripable[which]);
if (!route) {
return;
}
std::shared_ptr<InternalSend> send = std::dynamic_pointer_cast<InternalSend> (route->internal_send_for (target_bus));
if (!send) {
return;
}
std::shared_ptr<GainControl> gc = send->gain_control();
if (!gc) {
return;
}
gain_t gain;
if (shift_pressed) {
/* Just display current value */
gain = gc->get_value();
} else {
double pos = ARDOUR::gain_to_slider_position_with_max (gc->get_value(), ARDOUR::Config->get_max_gain());
pos += (step/127.0);
gain = ARDOUR::slider_position_to_gain_with_max (pos, ARDOUR::Config->get_max_gain());
session->set_control (gc, gain, Controllable::NoGroup);
}
char buf[16];
snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (gain));
set_display_target (DisplayTarget (0x15 + which), 1, string_compose ("> %1", send->target_route()->name()), true);
set_display_target (DisplayTarget (0x15 + which), 2, buf, true);
}
void
LaunchKey4::encoder_transport (int which, int step)
{
switch (which) {
case 0:
transport_shuttle (step);
break;
case 1:
zoom (step);
break;
case 2:
loop_start_move (step);
break;
case 3:
loop_end_move (step);
break;
case 4:
jump_to_marker (step);
break;
case 5:
break;
case 6:
break;
case 7:
break;
}
}
void
LaunchKey4::transport_shuttle (int step)
{
using namespace Temporal;
/* 1 step == 1/10th current page */
timepos_t pos (session->transport_sample());
if (pos == 0 && step < 0) {
return;
}
Beats b = pos.beats();
if (step > 0) {
b = b.round_up_to_beat ();
b += Beats (1, 0) * step;
} else {
b = b.round_down_to_beat ();
b += Beats (1, 0) * step; // step is negative, so add
if (b < Beats()) {
b = Beats();
}
}
BBT_Time bbt = TempoMap::use()->bbt_at (b);
std::stringstream str;
str << bbt;
set_display_target (DisplayTarget (0x15), 2, str.str(), true);
session->request_locate (timepos_t (b).samples());
}
void
LaunchKey4::zoom (int step)
{
if (step > 0) {
while (step--) {
temporal_zoom_in ();
}
} else {
while (step++ < 0) {
temporal_zoom_out ();
}
}
set_display_target (DisplayTarget (0x15 + 1), 2, string(), true);
}
void
LaunchKey4::loop_start_move (int step)
{
using namespace Temporal;
Location* l = session->locations()->auto_loop_location ();
BBT_Offset dur;
if (!l) {
/* XXX NEEDS WRAPPING IN REVERSIBLE COMMAND */
timepos_t ph (session->transport_sample());
timepos_t beat_later ((ph.beats() + Beats (1,0)).round_to_beat());
Location* loc = new Location (*session, timepos_t (ph.beats()), beat_later, _("Loop"), Location::IsAutoLoop);
session->locations()->add (loc, true);
session->set_auto_loop_location (loc);
dur = BBT_Offset (0, 1, 0);
} else {
timepos_t start = l->start();
start = start.beats() + Beats (step, 0);
if (start.is_zero() || start.is_negative()) {
return;
}
l->set_start (start);
TempoMap::SharedPtr map (TempoMap::use());
BBT_Time bbt_start = map->bbt_at (start);
BBT_Time bbt_end = map->bbt_at (l->end());
dur = bbt_delta (bbt_end, bbt_start);
}
std::stringstream str;
str << dur;
set_display_target (DisplayTarget (0x15 + 2), 2, str.str(), true);
}
void
LaunchKey4::loop_end_move (int step)
{
using namespace Temporal;
Location* l = session->locations()->auto_loop_location ();
BBT_Offset dur;
if (!l) {
/* XXX NEEDS WRAPPING IN REVERSIBLE COMMAND */
timepos_t ph (session->transport_sample());
timepos_t beat_later ((ph.beats() + Beats (1,0)).round_to_beat());
Location* loc = new Location (*session, timepos_t (ph.beats()), beat_later, _("Loop"), Location::IsAutoLoop);
session->locations()->add (loc, true);
session->set_auto_loop_location (loc);
dur = BBT_Offset (0, 1, 0);
} else {
timepos_t end = l->end();
end = end.beats() + Beats (step, 0);
if (end.is_zero() || end.is_negative()) {
return;
}
l->set_end (end);
TempoMap::SharedPtr map (TempoMap::use());
BBT_Time bbt_start = map->bbt_at (l->start());
BBT_Time bbt_end = map->bbt_at (end);
dur = bbt_delta (bbt_end, bbt_start);
}
std::stringstream str;
str << dur;
set_display_target (DisplayTarget (0x15 + 3), 2, str.str(), true);
}
void
LaunchKey4::jump_to_marker (int step)
{
timepos_t pos;
Location::Flags noflags = Location::Flags (0);
Location* loc;
if (step > 0) {
pos = session->locations()->first_mark_after_flagged (timepos_t (session->audible_sample()+1), true, noflags, noflags, noflags, &loc);
if (pos == timepos_t::max (Temporal::AudioTime)) {
return;
}
} else {
pos = session->locations()->first_mark_before_flagged (timepos_t (session->audible_sample()), true, noflags, noflags, noflags, &loc);
//handle the case where we are rolling, and we're less than one-half second past the mark, we want to go to the prior mark...
if (session->transport_rolling()) {
if ((session->audible_sample() - pos.samples()) < session->sample_rate()/2) {
timepos_t prior = session->locations()->first_mark_before (pos);
pos = prior;
}
}
if (pos == timepos_t::max (Temporal::AudioTime)) {
return;
}
}
session->request_locate (pos.samples());
set_display_target (DisplayTarget (0x15+4), 2, loc->name(), true);
}
void
LaunchKey4::set_pad_function (PadFunction f)
{
MIDI::byte msg[3];
std::string str;
/* make the LK forget about any currently lit pads, because we overload
mode 0x2 and it gets confusing when it tries to restore lighting.
*/
all_pads (0x5);
all_pads_out ();
msg[0] = 0xb6;
msg[1] = 0x40; /* set pad layout */
switch (f) {
case MuteSolo:
str = "Mute/Solo";
break;
case Triggers:
str = "Cues & Scenes";
break;
}
pad_function = f;
if (pad_function == Triggers) {
map_triggers ();
} else if (pad_function == MuteSolo) {
map_mute_solo ();
}
/* Turn up/down arrows on/off depending on pad mode, also scene mode */
msg[0] = 0xb0;
msg[2] = (pad_function == Triggers ? 0x3 : 0x0);
msg[1] = 0x6a; /* upper */
daw_write (msg, 3);
msg[1] = 0x6b; /* lower */
daw_write (msg, 3);
msg[1] = 0x68; /* scene */
daw_write (msg, 3);
configure_display (DAWPadFunctionDisplay, 0x1);
set_display_target (DAWPadFunctionDisplay, 0, str, true);
}
void
LaunchKey4::select_display_target (DisplayTarget dt)
{
MidiByteArray msg;
msg.push_back (0xf0);
msg.push_back (0x0);
msg.push_back (0x20);
msg.push_back (0x29);
msg.push_back ((device_pid >> 8) & 0x7f);
msg.push_back (device_pid & 0x7f);
msg.push_back (0x4);
msg.push_back (dt);
msg.push_back (0x7f);
msg.push_back (0xf7);
daw_write (msg);
}
void
LaunchKey4::set_plugin_encoder_name (int encoder, int field, std::string const & str)
{
set_display_target (PluginPotMode, field, str, true);
}
void
LaunchKey4::set_display_target (DisplayTarget dt, int field, std::string const & str, bool display)
{
MidiByteArray msg;
msg.push_back (0xf0);
msg.push_back (0x0);
msg.push_back (0x20);
msg.push_back (0x29);
msg.push_back ((device_pid >> 8) & 0x7f);
msg.push_back (device_pid & 0x7f);
msg.push_back (0x6);
msg.push_back (dt);
msg.push_back (display ? ((1<<6) | (field & 0x7f)) : (field & 0x7f));
for (auto c : str) {
msg.push_back (c & 0x7f);
}
msg.push_back (0xf7);
daw_write (msg);
write (msg);
}
void
LaunchKey4::configure_display (DisplayTarget target, int config)
{
MidiByteArray msg (9, 0xf0, 0x00, 0x29, 0xff, 0xff, 0x04, 0xff, 0xff, 0xf7);
msg[3] = (device_pid >> 8) & 0x7f;
msg[4] = device_pid & 0x7f;
msg[6] = target;
msg[7] = config & 0x7f;
daw_write (msg);
}
void
LaunchKey4::function_press ()
{
switch (pad_function) {
case MuteSolo:
set_pad_function (Triggers);
break;
case Triggers:
set_pad_function (MuteSolo);
break;
}
}
void
LaunchKey4::undo_press ()
{
if (shift_pressed) {
redo ();
} else {
undo ();
}
}
void
LaunchKey4::button_press (int n)
{
std::shared_ptr<AutomationControl> ac;
if (!stripable[n]) {
return;
}
switch (button_mode) {
case ButtonsSelect:
session->selection().select_stripable_and_maybe_group (stripable[n], SelectionSet);
break;
case ButtonsRecEnable:
ac = stripable[n]->rec_enable_control();
if (ac) {
ac->set_value (!ac->get_value(), Controllable::NoGroup);
}
break;
}
}
void
LaunchKey4::button_release (int n)
{
}
void
LaunchKey4::solo_changed ()
{
map_mute_solo ();
}
void
LaunchKey4::mute_changed (uint32_t n)
{
show_mute (n);
}
void
LaunchKey4::rec_enable_changed (uint32_t n)
{
show_rec_enable (n);
}
void
LaunchKey4::switch_bank (uint32_t base)
{
stripable_connections.drop_connections ();
/* work backwards so we can tell if we should actually switch banks */
std::shared_ptr<Stripable> s[8];
for (int n = 0; n < 8; ++n) {
s[n] = session->get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
}
if (!s[0]) {
/* not even the first stripable exists, do nothing */
return;
}
for (int n = 0; n < 8; ++n) {
stripable[n] = s[n];
}
/* at least one stripable in this bank */
bank_start = base;
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
/* stripable goes away? refill the bank, starting at the same point */
stripable[n]->DropReferences.connect (stripable_connections, invalidator (*this), std::bind (&LaunchKey4::switch_bank, this, bank_start), this);
stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, invalidator (*this), std::bind (&LaunchKey4::stripable_property_change, this, _1, n), this);
stripable[n]->mute_control()->Changed.connect (stripable_connections, invalidator (*this), std::bind (&LaunchKey4::mute_changed, this, n), this);
std::shared_ptr<AutomationControl> ac = stripable[n]->rec_enable_control();
if (ac) {
ac->Changed.connect (stripable_connections, invalidator (*this), std::bind (&LaunchKey4::rec_enable_changed, this, n), this);
}
}
/* Set fader "title" fields to show current bank */
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
set_display_target (DisplayTarget (0x5 + n), 0, stripable[n]->name(), true);
} else {
set_display_target (DisplayTarget (0x5 + n), 0, string(), true);
}
}
if (session->monitor_out()) {
set_display_target (DisplayTarget (0x5 + 8), 0, session->monitor_out()->name(), true);
} else if (session->master_out()) {
set_display_target (DisplayTarget (0x5 + 8), 0, session->master_out()->name(), true);
}
}
if (button_mode == ButtonsSelect) {
map_selection ();
} else {
map_rec_enable ();
}
switch (pad_function) {
case Triggers:
map_triggers ();
break;
case MuteSolo:
map_mute_solo ();
break;
default:
break;
}
if (encoder_mode != EncoderTransport) {
set_encoder_titles_to_route_names ();
}
}
void
LaunchKey4::stripable_property_change (PropertyChange const& what_changed, uint32_t which)
{
if (what_changed.contains (Properties::color)) {
show_selection (which);
}
if (what_changed.contains (Properties::hidden)) {
switch_bank (bank_start);
}
if (what_changed.contains (Properties::selected)) {
if (!stripable[which]) {
return;
}
}
}
void
LaunchKey4::stripables_added ()
{
/* reload current bank */
switch_bank (bank_start);
}
void
LaunchKey4::button_right ()
{
if (pad_function == Triggers) {
switch_bank (bank_start + 1);
scroll_x_offset = bank_start;
} else {
switch_bank (bank_start + 8);
}
std::cerr << "rright to " << bank_start << std::endl;
if (stripable[0]) {
set_display_target (GlobalTemporaryDisplay, 0, stripable[0]->name(), true);
}
}
void
LaunchKey4::button_left ()
{
if (pad_function == Triggers) {
if (bank_start > 0) {
switch_bank (bank_start - 1);
scroll_x_offset = bank_start;
}
} else {
if (bank_start > 7) {
switch_bank (bank_start - 8);
}
}
std::cerr << "left to " << bank_start << std::endl;
if (stripable[0]) {
set_display_target (GlobalTemporaryDisplay, 0, stripable[0]->name(), true);
}
}
void
LaunchKey4::toggle_button_mode ()
{
switch (button_mode) {
case ButtonsSelect:
button_mode = ButtonsRecEnable;
map_rec_enable ();
break;
case ButtonsRecEnable:
button_mode = ButtonsSelect;
map_selection ();
break;
}
MIDI::byte msg[3];
msg[0] = 0xb0;
msg[1] = Button9;
if (button_mode == ButtonsSelect) {
msg[2] = 0x3; /* brght white */
} else {
msg[2] = 0x5; /* red */
}
daw_write (msg, 3);
}
void
LaunchKey4::map_selection ()
{
for (int n = 0; n < 8; ++n) {
show_selection (n);
}
}
void
LaunchKey4::show_selection (int n)
{
const int first_button = 0x25;
const int selection_color = 0xd; /* bright yellow */
if (!stripable[n]) {
light_button (first_button + n, Off, 0);
} else if (stripable[n]->is_selected()) {
light_button (first_button + n, Solid, selection_color);
} else {
light_button (first_button + n, Solid, find_closest_palette_color (stripable[n]->presentation_info().color ()));
}
}
void
LaunchKey4::map_mute_solo ()
{
for (int n = 0; n < 8; ++n) {
show_mute (n);
show_solo (n);
}
}
void
LaunchKey4::show_mute (int n)
{
if (!stripable[n]) {
return;
}
std::shared_ptr<MuteControl> mc (stripable[n]->mute_control());
if (!mc) {
return;
}
MIDI::byte msg[3];
msg[0] = 0x90;
msg[1] = 0x60 + n;
if (mc->muted_by_self()) {
// std::cerr << stripable[n]->name() << " muted by self\n";
msg[2] = 0xd; /* bright yellow */
} else if (mc->muted_by_others_soloing() || mc->muted_by_masters()) {
// std::cerr << stripable[n]->name() << " muted by others\n";
msg[2] = 0x49; /* soft yellow */
} else {
// std::cerr << stripable[n]->name() << " not muted\n";
msg[2] = 0x0;;
}
daw_write (msg, 3);
}
void
LaunchKey4::show_solo (int n)
{
if (!stripable[n]) {
return;
}
std::shared_ptr<SoloControl> sc (stripable[n]->solo_control());
if (!sc) {
return;
}
MIDI::byte msg[3];
msg[0] = 0x90;
msg[1] = 0x70 + n;
if (sc->soloed_by_self_or_masters()) {
msg[2] = 0x15; /* bright green */
} else if (sc->soloed_by_others()) {
msg[2] = 0x4b; /* soft green */
} else {
msg[2] = 0x0;
}
daw_write (msg, 3);
}
void
LaunchKey4::light_button (int which, LightingMode mode, int color_index)
{
MIDI::byte msg[3];
msg[1] = which;
switch (mode) {
case Off:
msg[0] = 0xb0;
msg[2] = 0x0;
break;
case Solid:
msg[0] = 0xb0;
msg[2] = color_index & 0x7f;
break;
case Flash:
msg[0] = 0xb1;
msg[2] = color_index & 0x7f;
break;
case Pulse:
msg[0] = 0xb2;
msg[2] = color_index & 0x7f;
break;
}
daw_write (msg, 3);
}
void
LaunchKey4::light_pad (int pid, LightingMode mode, int color_index)
{
MIDI::byte msg[3];
msg[1] = pid;
switch (mode) {
case Off:
msg[0] = 0x90;
msg[2] = 0x0;
break;
case Solid:
msg[0] = 0x90;
msg[2] = color_index & 0x7f;
break;
case Flash:
msg[0] = 0x91;
msg[2] = color_index & 0x7f;
break;
case Pulse:
msg[0] = 0x92;
msg[2] = color_index & 0x7f;
break;
}
daw_write (msg, 3);
}
void
LaunchKey4::unlight_pad (int pad_id)
{
light_pad (pad_id, Solid, 0x0);
}
void
LaunchKey4::set_encoder_bank (int n)
{
bool light_up_arrow = false;
bool light_down_arrow = false;
encoder_bank = n;
/* Ordering:
9
1
2
*/
if (encoder_mode == EncoderPlugins) {
switch (encoder_bank) {
case 0:
if (num_plugin_controls > 8) {
light_down_arrow = true;
}
break;
case 1:
if (num_plugin_controls > 8) {
light_up_arrow = true;
}
if (num_plugin_controls > 16) {
light_down_arrow = true;
}
break;
case 2:
if (num_plugin_controls > 16) {
light_up_arrow = true;
}
break;
}
} else if (encoder_mode == EncoderMixer) {
switch (encoder_bank) {
case 0:
light_down_arrow = true;
break;
case 1:
light_down_arrow = true;
light_up_arrow = true;
break;
case 2:
light_up_arrow = true;
break;
default:
return;
}
}
MIDI::byte msg[6];
/* Color doesn't really matter, these LEDs are single-color. Just turn
it on or off.
*/
const int color_index = 0x3;
msg[0] = 0xb0;
msg[1] = 0x33; /* top */
msg[3] = 0xb0;
msg[4] = 0x34; /* bottom */
if (light_up_arrow) {
msg[2] = color_index;
} else {
msg[2] = 0x0;
}
if (light_down_arrow) {
msg[5] = color_index;
} else {
msg[5] = 0x0;
}
/* Stupid device doesn't seem to like both messages "at once" */
daw_write (msg, 3);
daw_write (&msg[3], 3);
label_encoders ();
}
void
LaunchKey4::label_encoders ()
{
std::shared_ptr<Plugin> plugin (current_plugin.lock());
switch (encoder_mode) {
case EncoderMixer:
case EncoderSendA:
set_encoder_titles_to_route_names ();
switch (encoder_bank) {
case 0:
for (int n = 0; n < 8; ++n) {
set_display_target (DisplayTarget (0x15 + n), 1, "Level", false);
}
set_display_target (GlobalTemporaryDisplay, 0, "Levels", true);
break;
case 1:
for (int n = 0; n < 8; ++n) {
set_display_target (DisplayTarget (0x15 + n), 1, "Pan", false);
}
set_display_target (GlobalTemporaryDisplay, 0, "Panning", true);
break;
default:
break;
}
break;
case EncoderPlugins:
setup_screen_for_encoder_plugins ();
break;
case EncoderTransport:
set_display_target (DisplayTarget (0x15), 1, "Shuttle", true);
set_display_target (DisplayTarget (0x16), 1, "Zoom", true);
set_display_target (DisplayTarget (0x17), 1, "Loop Start", true);
set_display_target (DisplayTarget (0x18), 1, "Loop End", true);
set_display_target (DisplayTarget (0x19), 1, "Jump to Marker", true);
set_display_target (DisplayTarget (0x1a), 1, string(), true);
set_display_target (DisplayTarget (0x1b), 1, string(), true);
set_display_target (DisplayTarget (0x1c), 1, string(), true);
for (int n = 0; n < 8; ++n) {
set_display_target (DisplayTarget (0x15 + n), 0, "Transport", true);
}
set_display_target (GlobalTemporaryDisplay, 0, "Transport", true);
break;
}
}
void
LaunchKey4::set_encoder_mode (EncoderMode m)
{
encoder_mode = m;
set_encoder_bank (0);
/* device firmware reset to continuous controller mode, so switch back
* if (ev->controller_to encoders
*/
use_encoders (true);
label_encoders ();
}
void
LaunchKey4::set_encoder_titles_to_route_names ()
{
/* Set encoder "title" fields to show current bank */
bool first = true;
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
set_display_target (DisplayTarget (0x15 + n), 0, stripable[n]->name(), first);
first = false;
} else {
set_display_target (DisplayTarget (0x15 + n), 0, string(), true);
}
}
}
void
LaunchKey4::in_msecs (int msecs, std::function<void()> func)
{
Glib::RefPtr<Glib::TimeoutSource> timeout = Glib::TimeoutSource::create (msecs); // milliseconds
timeout->connect (sigc::bind_return (func, false));
timeout->attach (main_loop()->get_context());
}
void
LaunchKey4::scene_press ()
{
if (shift_pressed) {
trigger_stop_all (true); /* immediate stop */
} else {
trigger_cue_row (scroll_y_offset);
}
}