launchpad X: 98% functionality

This commit is contained in:
Paul Davis 2023-10-31 09:56:11 -06:00
parent dec9282110
commit 43c5f0ab46
2 changed files with 247 additions and 104 deletions

View File

@ -78,6 +78,7 @@ using namespace Gtkmm2ext;
#define NOVATION 0x1235
#define LAUNCHPADX 0x0103
static const std::vector<MIDI::byte> sysex_header ({ 0xf0, 0x00, 0x20, 0x29, 0x2, 0xc });
static int first_fader = 0x9;
bool
LaunchPadX::available ()
@ -135,6 +136,7 @@ LaunchPadX::LaunchPadX (ARDOUR::Session& s)
, _session_mode (SessionMode)
, current_fader_bank (VolumeFaders)
, pre_fader_layout (SessionLayout)
, pending_mixer_op (PendingNone)
{
run_event_loop ();
port_setup ();
@ -351,9 +353,9 @@ LaunchPadX::build_pad_map ()
BUTTON (Pan, &LaunchPadX::rh1_press);
BUTTON (SendA, &LaunchPadX::rh2_press);
BUTTON (SendB, &LaunchPadX::rh3_press);
BUTTON (StopClip, &LaunchPadX::rh4_press);
BUTTON2 (StopClip, &LaunchPadX::rh4_press, &LaunchPadX::rh4_long_press);
BUTTON (Mute, &LaunchPadX::rh5_press);
BUTTON(Solo, &LaunchPadX::rh6_press);
BUTTON2 (Solo, &LaunchPadX::rh6_press, &LaunchPadX::rh6_long_press);
BUTTON (RecordArm, &LaunchPadX::rh7_press);
/* Now add the 8x8 central pad grid */
@ -361,7 +363,7 @@ LaunchPadX::build_pad_map ()
for (int row = 0; row < 8; ++row) {
for (int col = 0; col < 8; ++col) {
int pid = (11 + (row * 10)) + col;
std::pair<int,Pad> p (pid, Pad (pid, col, 7 - row, &LaunchPadX::pad_press, &LaunchPadX::pad_long_press, &LaunchPadX::relax));
std::pair<int,Pad> p (pid, Pad (pid, col, 7 - row, &LaunchPadX::pad_press, &LaunchPadX::pad_long_press));
if (!pad_map.insert (p).second) abort();
}
}
@ -520,71 +522,96 @@ LaunchPadX::display_session_layout ()
* it across power-cycling!
*/
std::list<PadID> rhs { Volume, Pan, SendA, SendB, StopClip, Mute, Solo, RecordArm };
MIDI::byte msg[3];
msg[0] = 0xb0;
MIDI::byte color = (_session_mode == SessionMode ? 0x27 : 0x9);
if (pending_mixer_op == PendingNone) {
msg[1] = Session;
msg[2] = color;
daw_write (msg, 3);
MIDI::byte color = (_session_mode == SessionMode ? 0x27 : 0x9);
msg[1] = Volume;
msg[2] = color;
daw_write (msg, 3);
msg[1] = Pan;
msg[2] = color;
daw_write (msg, 3);
msg[1] = SendA;
msg[2] = color;
daw_write (msg, 3);
msg[1] = SendB;
msg[2] = color;
daw_write (msg, 3);
msg[1] = StopClip;
msg[2] = color;
daw_write (msg, 3);
msg[1] = Mute;
msg[2] = color;
daw_write (msg, 3);
msg[1] = Solo;
msg[2] = color;
daw_write (msg, 3);
msg[1] = RecordArm;
msg[2] = color;
daw_write (msg, 3);
msg[1] = Session;
msg[2] = color;
daw_write (msg, 3);
msg[1] = CaptureMIDI;
msg[2] = 5;
daw_write (msg, 3);
for (auto pid : rhs) {
msg[1] = pid;
msg[2] = color;
daw_write (msg, 3);
}
msg[1] = Up;
msg[2] = 46;
daw_write (msg, 3);
msg[1] = Down;
msg[2] = 46;
daw_write (msg, 3);
msg[1] = Left;
msg[2] = 46;
daw_write (msg, 3);
msg[1] = Right;
msg[2] = 46;
msg[1] = CaptureMIDI;
msg[2] = 5;
daw_write (msg, 3);
msg[1] = Up;
msg[2] = 46;
daw_write (msg, 3);
msg[1] = Down;
msg[2] = 46;
daw_write (msg, 3);
msg[1] = Left;
msg[2] = 46;
daw_write (msg, 3);
msg[1] = Right;
msg[2] = 46;
daw_write (msg, 3);
return;
}
PadID special;
MIDI::byte color;
switch (pending_mixer_op) {
case PendingStopClip:
special = StopClip;
color = 0x3c;
break;
case PendingSolo:
special = Solo;
color = 0x13;
break;
case PendingMute:
special = Mute;
color = 0x25;
break;
case PendingRecArm:
special = RecordArm;
color = 0x5;
break;
default:
return;
}
rhs.remove (special);
for (auto pid : rhs) {
msg[1] = pid;
msg[2] = 0x2; /* dim */
daw_write (msg, 3);
}
msg[1] = special;
msg[2] = color;
daw_write (msg, 3);
}
void
LaunchPadX::handle_midi_controller_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev)
{
DEBUG_TRACE (DEBUG::Launchpad, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
if (&parser != _daw_in_port->parser()) {
/* we don't process CC messages from the regular port */
DEBUG_TRACE (DEBUG::Launchpad, string_compose ("skip non-DAW CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
return;
}
DEBUG_TRACE (DEBUG::Launchpad, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
if (_current_layout == SessionLayout && _session_mode == MixerMode) {
/* Trap fader move messages and act on them */
if (ev->controller_number >= 0x20 && ev->controller_number < 0x28) {
if (ev->controller_number >= first_fader && ev->controller_number < first_fader+8) {
fader_move (ev->controller_number, ev->value);
return;
}
@ -620,10 +647,16 @@ LaunchPadX::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoByt
return;
}
if (&parser != _daw_in_port->parser()) {
/* we don't process CC messages from the regular port */
DEBUG_TRACE (DEBUG::Launchpad, 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;
}
DEBUG_TRACE (DEBUG::Launchpad, string_compose ("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));
if (_current_layout != SessionLayout) {
std::cerr << "center pad, ignore because cl = " << _current_layout << std::endl;
return;
}
@ -677,7 +710,6 @@ LaunchPadX::connect_daw_ports ()
{
if (!_daw_in || !_daw_out) {
/* ports not registered yet */
std::cerr << "no daw port registered\n";
return;
}
@ -996,11 +1028,21 @@ LaunchPadX::rh4_press (Pad& pad)
}
}
void
LaunchPadX::rh4_long_press (Pad& pad)
{
std::cerr << "\n\n>>>> stop long\n";
if (session) {
session->trigger_stop_all (true);
}
consumed.insert (pad.id);
}
void
LaunchPadX::rh5_press (Pad& pad)
{
if (_current_layout == SessionLayout) {
if (_session_mode == SessionMode) {
if (!pending_mixer_op && _session_mode == SessionMode) {
cue_press (pad, 5 + scroll_y_offset);
} else {
mute_press (pad);
@ -1012,7 +1054,7 @@ void
LaunchPadX::rh6_press (Pad& pad)
{
if (_current_layout == SessionLayout) {
if (_session_mode == SessionMode) {
if (!pending_mixer_op && _session_mode == SessionMode) {
cue_press (pad, 6 + scroll_y_offset);
} else {
solo_press (pad);
@ -1020,11 +1062,20 @@ LaunchPadX::rh6_press (Pad& pad)
}
}
void
LaunchPadX::rh6_long_press (Pad& pad)
{
std::cerr << "\n\n>>>> solo long\n";
cancel_all_solo ();
/* Pad was used for long press, do not invoke release action */
consumed.insert (pad.id);
}
void
LaunchPadX::rh7_press (Pad& pad)
{
if (_current_layout == SessionLayout) {
if (_session_mode == SessionMode) {
if (!pending_mixer_op && _session_mode == SessionMode) {
cue_press (pad, 7 + scroll_y_offset);
} else {
record_arm_press (pad);
@ -1032,14 +1083,6 @@ LaunchPadX::rh7_press (Pad& pad)
}
}
void
LaunchPadX::stop_clip_press (Pad& pad)
{
if (session) {
session->trigger_stop_all (true);
}
}
void
LaunchPadX::fader_mode_press (FaderBank bank)
{
@ -1080,48 +1123,28 @@ LaunchPadX::send_b_press (Pad& pad)
fader_mode_press (SendBFaders);
}
void
LaunchPadX::stop_clip_press (Pad& pad)
{
set_pending_mixer_op (PendingStopClip);
}
void
LaunchPadX::mute_press (Pad& pad)
{
std::shared_ptr<Stripable> s = session->selection().first_selected_stripable();
if (s) {
std::shared_ptr<AutomationControl> ac = s->mute_control();
if (ac) {
ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
}
}
set_pending_mixer_op (PendingMute);
}
void
LaunchPadX::solo_press (Pad& pad)
{
std::shared_ptr<Stripable> s = session->selection().first_selected_stripable();
if (s) {
std::shared_ptr<AutomationControl> ac = s->solo_control();
if (ac) {
session->set_control (ac, !ac->get_value(), PBD::Controllable::UseGroup);
}
}
}
void
LaunchPadX::solo_long_press (Pad& pad)
{
cancel_all_solo ();
/* Pad was used for long press, do not invoke release action */
consumed.insert (pad.id);
set_pending_mixer_op (PendingSolo);
}
void
LaunchPadX::record_arm_press (Pad& pad)
{
std::shared_ptr<Stripable> s = session->selection().first_selected_stripable();
if (s) {
std::shared_ptr<AutomationControl> ac = s->rec_enable_control();
if (ac) {
ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
}
}
set_pending_mixer_op (PendingRecArm);
}
void
@ -1148,17 +1171,28 @@ void
LaunchPadX::pad_press (Pad& pad, int velocity)
{
DEBUG_TRACE (DEBUG::Launchpad, string_compose ("pad press on %1, %2 => %3 vel %4\n", pad.x, pad.y, pad.id, velocity));
session->bang_trigger_at (pad.x, pad.y, velocity / 127.0f);
start_press_timeout (pad);
/* Check for bottom row press */
if (pending_mixer_op != PendingNone && pad.y == 7) {
handle_pending_mixer_op (pad.x);
} else {
session->bang_trigger_at (pad.x, pad.y, velocity / 127.0f);
start_press_timeout (pad);
}
}
void
LaunchPadX::pad_long_press (Pad& pad)
{
DEBUG_TRACE (DEBUG::Launchpad, string_compose ("pad long press on %1, %2 => %3\n", pad.x, pad.y, pad.id));
session->unbang_trigger_at (pad.x, pad.y);
/* Pad was used for long press, do not invoke release action */
consumed.insert (pad.id);
if (pending_mixer_op != PendingNone && pad.y == 7) {
/* relax */
} else {
session->unbang_trigger_at (pad.x, pad.y);
/* Pad was used for long press, do not invoke release action */
consumed.insert (pad.id);
}
}
void
@ -1167,7 +1201,7 @@ LaunchPadX::trigger_property_change (PropertyChange pc, Trigger* t)
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));
// DEBUG_TRACE (DEBUG::Launchpad, string_compose ("prop change %1 for trigger at %2, %3\n", pc, x, y));
if (y > scroll_y_offset + 7) {
/* not visible at present */
@ -1513,7 +1547,7 @@ LaunchPadX::route_property_change (PropertyChange const & pc, int col)
}
void
LaunchPadX::set_session_mode (SessionState sm)
LaunchPadX::set_session_mode (SessionState sm, bool clear_pending)
{
MidiByteArray msg (sysex_header);
msg.push_back (0x0);
@ -1521,9 +1555,12 @@ LaunchPadX::set_session_mode (SessionState sm)
msg.push_back (0xf7);
daw_write (msg);
if (clear_pending) {
pending_mixer_op = PendingNone;
}
_session_mode = sm;
_current_layout = SessionLayout;
std::cerr << "layout " << _current_layout << " sm " << _session_mode << std::endl;
display_session_layout ();
if (_session_mode == SessionMode) {
@ -1558,7 +1595,7 @@ LaunchPadX::setup_faders (FaderBank bank)
msg.push_back (0); /* unipolar */
break;
}
msg.push_back (0x20+n); /* CC number */
msg.push_back (0x18+n); /* CC number */
msg.push_back (random() % 127); /* color */
}
@ -1578,7 +1615,7 @@ LaunchPadX::fader_move (int cc, int val)
r = std::dynamic_pointer_cast<Route> (session->selection().first_selected_stripable());
break;
default:
r = session->get_remote_nth_route (scroll_x_offset + (cc - 0x20));
r = session->get_remote_nth_route (scroll_x_offset + (cc - first_fader));
break;
}
@ -1602,7 +1639,7 @@ LaunchPadX::fader_move (int cc, int val)
}
break;
case SendAFaders:
ac = r->send_level_controllable (scroll_x_offset + (cc - 0x20));
ac = r->send_level_controllable (scroll_x_offset + (cc - first_fader));
if (ac) {
session->set_control (ac, ARDOUR::slider_position_to_gain_with_max (val/127.0, ARDOUR::Config->get_max_gain()), PBD::Controllable::NoGroup);
}
@ -1638,7 +1675,7 @@ LaunchPadX::map_faders ()
std::shared_ptr<AutomationControl> ac;
msg[1] = 0x20 + n;
msg[1] = first_fader + n;
if (!r) {
switch (current_fader_bank) {
@ -1671,7 +1708,15 @@ LaunchPadX::map_faders ()
}
break;
case SendAFaders:
ac = r->send_level_controllable (n);
ac = r->send_level_controllable (0);
if (ac) {
msg[2] = (MIDI::byte) (ARDOUR::gain_to_slider_position_with_max (ac->get_value(), ARDOUR::Config->get_max_gain()) * 127.0);
} else {
msg[2] = 0;
}
break;
case SendBFaders:
ac = r->send_level_controllable (1);
if (ac) {
msg[2] = (MIDI::byte) (ARDOUR::gain_to_slider_position_with_max (ac->get_value(), ARDOUR::Config->get_max_gain()) * 127.0);
} else {
@ -1701,7 +1746,7 @@ LaunchPadX::automation_control_change (int n, std::weak_ptr<AutomationControl> w
MIDI::byte msg[3];
msg[0] = 0xb4;
msg[1] = 0x20 + n;
msg[1] = first_fader + n;
switch (current_fader_bank) {
case VolumeFaders:
@ -1715,5 +1760,86 @@ LaunchPadX::automation_control_change (int n, std::weak_ptr<AutomationControl> w
default:
break;
}
daw_write (msg, 3);
}
void
LaunchPadX::set_pending_mixer_op (PendingMixerOp mop)
{
pending_mixer_op = mop;
MIDI::byte msg[3];
msg[0] = 0x90;
switch (mop) {
case PendingNone:
/* display_session_layout() will already have done the right thing */
return;
case PendingStopClip:
msg[2] = 0x3c;
break;
case PendingSolo:
msg[2] = 0x13;
break;
case PendingMute:
msg[2] = 0x25;
break;
case PendingRecArm:
msg[2] = 0x5;
break;
}
set_session_mode (SessionMode, false);
/* Color the bottom row of pads */
for (int n = 0xb; n < 0x13; ++n) {
msg[1] = n;
daw_write (msg, 3);
}
}
void
LaunchPadX::handle_pending_mixer_op (int col)
{
int nth = col + scroll_x_offset;
std::shared_ptr<Stripable> s = session->get_remote_nth_route (nth);
if (!s) {
return;
}
std::shared_ptr<AutomationControl> ac;
std::shared_ptr<Route> r;
switch (pending_mixer_op) {
case PendingNone:
return;
case PendingStopClip:
r = std::dynamic_pointer_cast<Route> (s);
if (r) {
std::shared_ptr<TriggerBox> tb = r->triggerbox();
if (tb) {
tb->stop_all_quantized ();
}
}
return;
case PendingMute:
ac = s->mute_control();
break;
case PendingSolo:
ac = s->solo_control();
break;
case PendingRecArm:
ac = s->rec_enable_control();
break;
}
if (ac) {
ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
}
}

View File

@ -264,7 +264,10 @@ class LaunchPadX : public MIDISurface
};
void set_device_mode (DeviceMode);
void set_session_mode (SessionState);
void set_session_mode (SessionState, bool clear_pending);
void set_session_mode (SessionState sm) {
set_session_mode (sm, true);
}
SessionState _session_mode;
@ -275,8 +278,10 @@ class LaunchPadX : public MIDISurface
void rh2_press (Pad&);
void rh3_press (Pad&);
void rh4_press (Pad&);
void rh4_long_press (Pad&);
void rh5_press (Pad&);
void rh6_press (Pad&);
void rh6_long_press (Pad&);
void rh7_press (Pad&);
void fader_mode_press (FaderBank);
@ -353,6 +358,18 @@ class LaunchPadX : public MIDISurface
FaderBank current_fader_bank;
bool revert_layout_on_fader_release;
Layout pre_fader_layout;
enum PendingMixerOp {
PendingNone,
PendingStopClip,
PendingMute,
PendingSolo,
PendingRecArm
};
PendingMixerOp pending_mixer_op;
void set_pending_mixer_op (PendingMixerOp);
void handle_pending_mixer_op (int col);
};