second attempt at fixing the launchpad pro port name mess

It turns out that slightly older versions of ALSA create different "pretty"
port names for USB MIDI devices than slightly newer ones. The new versions
use names that match those seen on other platforms.

This means that to do port matching on Linux now requires a regexp
to match the possible alternatives. This matters much more for the LPP,
which has 3 input ports and 3 output ports, than it does for most devices
that have a single input and single output, and we can "find" the ports
just using simple string searching
This commit is contained in:
Paul Davis 2023-10-13 21:15:58 -06:00
parent c90dc9a647
commit 1d6c2a946d
4 changed files with 130 additions and 72 deletions

View File

@ -16,6 +16,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <regex>
#include "pbd/debug.h"
#include "pbd/i18n.h"
@ -167,20 +169,79 @@ MIDISurface::port_registration_handler ()
return;
}
std::vector<std::string> in;
std::vector<std::string> out;
std::vector<std::string> midi_inputs;
std::vector<std::string> midi_outputs;
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);
AudioEngine::instance()->get_ports ("", DataType::MIDI, PortFlags (IsPhysical|IsOutput), midi_inputs);
AudioEngine::instance()->get_ports ("", DataType::MIDI, PortFlags (IsPhysical|IsInput), midi_outputs);
if (!in.empty() && !out.empty()) {
if (!_async_in->connected()) {
AudioEngine::instance()->connect (_async_in->name(), in.front());
if (midi_inputs.empty() || midi_outputs.empty()) {
return;
}
/* Try to find the input & output ports, whose pretty name varies on
* Linux depending on the version of ALSA, but is fairly consistent
* across newer ALSA and other platforms.
*/
/* See if the input port is available, and maybe connect that */
string ip = input_port_name ();
if (ip[0] == ':') {
std::regex rx (ip.substr (1), std::regex::extended);
auto is_the_input = [&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_the_input);
if (pi != midi_inputs.end()) {
AudioEngine::instance()->connect (_async_in->name(), *pi);
}
if (!_async_out->connected()) {
AudioEngine::instance()->connect (_async_out->name(), out.front());
} else {
/* regular partial string search */
auto is_the_input = [&ip](string const &s) {
std::string pn = AudioEngine::instance()->get_hardware_port_name_by_name(s);
return pn.find (ip) != string::npos;
};
auto pi = std::find_if (midi_inputs.begin(), midi_inputs.end(), is_the_input);
if (pi != midi_inputs.end()) {
AudioEngine::instance()->connect (_async_in->name(), *pi);
}
}
/* Now see if the output port is available, and maybe connect that */
string op = output_port_name ();
if (op[0] == ':') {
std::regex rx (op.substr (1), std::regex::extended);
auto is_the_output = [&rx](string const &s) {
std::string pn = AudioEngine::instance()->get_hardware_port_name_by_name(s);
return std::regex_search (pn, rx);
};
auto po = std::find_if (midi_outputs.begin(), midi_outputs.end(), is_the_output);
if (po != midi_outputs.end()) {
AudioEngine::instance()->connect (_async_in->name(), *po);
}
} else {
/* regular partial string search */
auto is_the_output = [&op](string const &s) {
std::string pn = AudioEngine::instance()->get_hardware_port_name_by_name(s);
return pn.find (op) != string::npos;
};
auto po = std::find_if (midi_outputs.begin(), midi_outputs.end(), is_the_output);
if (po != midi_outputs.end()) {
AudioEngine::instance()->connect (_async_in->name(), *po);
}
}
}
bool
@ -442,4 +503,3 @@ MIDISurface::bundles ()
return b;
}

View File

@ -58,6 +58,17 @@ class MIDISurface : public ARDOUR::ControlProtocol
ARDOUR::Session & get_session() { return *session; }
/* These two names are used in a port registration handler to try to
automatically connect the device when it is discovered.
If the value returned by these methods begins with a colon, they
will be assumed to be regular expressions, and passed (without the
leading colon) into the constructor of a std::regex using
std::regex::extended syntax.
Otherwise, they are assumed to be unique string identifiers, and are
merely searched for in port names with std::string::find().
*/
virtual std::string input_port_name () const = 0;
virtual std::string output_port_name () const = 0;

View File

@ -21,6 +21,7 @@
#include <bitset>
#include <cmath>
#include <limits>
#include <regex>
#include <stdlib.h>
#include <pthread.h>
@ -110,15 +111,26 @@ LaunchPadPro::probe (std::string& i, std::string& o)
{
vector<string> midi_inputs;
vector<string> midi_outputs;
AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_regex()), DataType::MIDI, PortFlags (IsOutput|IsTerminal), midi_inputs);
AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_regex()), DataType::MIDI, PortFlags (IsInput|IsTerminal), 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;
}
i = midi_inputs.front();
o = midi_inputs.front();
std::regex rx (X_("Launchpad Pro MK3.*MIDI"));
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);
i = *pi;
o = *po;
return true;
}
@ -322,53 +334,13 @@ LaunchPadPro::set_state (const XMLNode & node, int version)
std::string
LaunchPadPro::input_port_name () const
{
return input_port_regex();
}
std::string
LaunchPadPro::input_port_regex ()
{
#ifdef __APPLE__
return X_("Launchpad Pro MK3.*MIDI In");
#else
return X_("Launchpad Pro MK3.*MIDI 1");
#endif
}
std::string
LaunchPadPro::input_daw_port_regex ()
{
#ifdef __APPLE__
return X_("Launchpad Pro MK3.*DAW");
#else
return X_("Launchpad Pro MK3.*MIDI 3");
#endif
return X_(":Launchpad Pro MK3.*MIDI (In|1)");
}
std::string
LaunchPadPro::output_port_name () const
{
return output_port_regex();
}
std::string
LaunchPadPro::output_port_regex()
{
#ifdef __APPLE__
return X_("Launchpad Pro MK3.*MIDI Out");
#else
return X_("Launchpad Pro MK3.*MIDI 1");
#endif
}
std::string
LaunchPadPro::output_daw_port_regex ()
{
#ifdef __APPLE__
return X_("Launchpad Pro MK3.*DAW");
#else
return X_("Launchpad Pro MK3.*MIDI 3");
#endif
return X_(":Launchpad Pro MK3.*MIDI (Out|1)");
}
void
@ -856,21 +828,40 @@ LaunchPadPro::connect_daw_ports ()
return;
}
std::vector<std::string> in;
std::vector<std::string> out;
AudioEngine::instance()->get_ports (string_compose (".*%1", input_daw_port_regex()), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
AudioEngine::instance()->get_ports (string_compose (".*%1", output_daw_port_regex()), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
std::vector<std::string> midi_inputs;
std::vector<std::string> midi_outputs;
if (!in.empty() && !out.empty()) {
/* get all MIDI Ports */
if (!_daw_in->connected()) {
AudioEngine::instance()->connect (_daw_in->name(), in.front());
}
AudioEngine::instance()->get_ports ("", DataType::MIDI, PortFlags (IsOutput|IsTerminal), midi_inputs);
AudioEngine::instance()->get_ports("", DataType::MIDI, PortFlags(IsInput | IsTerminal), midi_outputs);
if (!_daw_out->connected()) {
AudioEngine::instance()->connect (_daw_out->name(), out.front());
}
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::regex rx (X_("Launchpad Pro MK3.*(DAW|MIDI 3)"), 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 (!_daw_in->connected()) {
AudioEngine::instance()->connect (_daw_in->name(), *pi);
}
if (!_daw_out->connected()) {
AudioEngine::instance()->connect (_daw_out->name(), *po);
}
}
int
@ -1074,7 +1065,7 @@ LaunchPadPro::stripable_selection_changed ()
}
}
}
bool

View File

@ -292,10 +292,6 @@ class LaunchPadPro : public MIDISurface
void port_registration_handler ();
int ports_acquire ();
void ports_release ();
static std::string input_port_regex ();
static std::string output_port_regex ();
static std::string input_daw_port_regex ();
static std::string output_daw_port_regex ();
void connect_daw_ports ();
void daw_write (const MidiByteArray&);