aboutsummaryrefslogtreecommitdiff
path: root/backends/midi/alsa.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backends/midi/alsa.cpp')
-rw-r--r--backends/midi/alsa.cpp321
1 files changed, 226 insertions, 95 deletions
diff --git a/backends/midi/alsa.cpp b/backends/midi/alsa.cpp
index c38537248c..4f73d7384b 100644
--- a/backends/midi/alsa.cpp
+++ b/backends/midi/alsa.cpp
@@ -24,7 +24,7 @@
#include "common/scummsys.h"
-#if defined(UNIX) && defined(USE_ALSA)
+#if defined(USE_ALSA)
#include "common/config-manager.h"
#include "common/util.h"
@@ -48,6 +48,17 @@
#define my_snd_seq_open(seqp) snd_seq_open(seqp, SND_SEQ_OPEN)
#endif
+#define perm_ok(pinfo,bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
+
+static int check_permission(snd_seq_port_info_t *pinfo)
+{
+ if (perm_ok(pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) {
+ if (!(snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_NO_EXPORT))
+ return 1;
+ }
+ return 0;
+}
+
/*
* parse address string
*/
@@ -56,7 +67,7 @@
class MidiDriver_ALSA:public MidiDriver_MPU401 {
public:
- MidiDriver_ALSA();
+ MidiDriver_ALSA(int client, int port);
int open();
void close();
void send(uint32 b);
@@ -69,34 +80,19 @@ private:
snd_seq_t *seq_handle;
int seq_client, seq_port;
int my_client, my_port;
- static int parse_addr(const char *arg, int *client, int *port);
};
-MidiDriver_ALSA::MidiDriver_ALSA()
- : _isOpen(false), seq_handle(0), seq_client(0), seq_port(0), my_client(0), my_port(0)
+MidiDriver_ALSA::MidiDriver_ALSA(int client, int port)
+ : _isOpen(false), seq_handle(0), seq_client(client), seq_port(port), my_client(0), my_port(0)
{
memset(&ev, 0, sizeof(ev));
}
int MidiDriver_ALSA::open() {
- const char *var = NULL;
-
if (_isOpen)
return MERR_ALREADY_OPEN;
_isOpen = true;
- var = getenv("SCUMMVM_PORT");
- if (!var && ConfMan.hasKey("alsa_port")) {
- var = ConfMan.get("alsa_port").c_str();
- }
-
- if (var) {
- if (parse_addr(var, &seq_client, &seq_port) < 0) {
- error("Invalid port %s", var);
- return -1;
- }
- }
-
if (my_snd_seq_open(&seq_handle) < 0) {
error("Can't open sequencer");
return -1;
@@ -108,9 +104,14 @@ int MidiDriver_ALSA::open() {
}
snd_seq_set_client_group(seq_handle, "input");
- my_port = snd_seq_create_simple_port(seq_handle, "SCUMMVM port 0",
- SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE |
- SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+ // According to http://www.alsa-project.org/~tiwai/alsa-subs.html
+ // you can set read or write capabilities to allow other clients to
+ // read or write the port. I don't think we need that, unless maybe
+ // to be able to record the sound, but I can't get that to work even
+ // with those capabilities.
+
+ my_port = snd_seq_create_simple_port(seq_handle, "SCUMMVM port 0", 0,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
if (my_port < 0) {
snd_seq_close(seq_handle);
@@ -118,29 +119,45 @@ int MidiDriver_ALSA::open() {
return -1;
}
- if (var) {
- if (seq_client != SND_SEQ_ADDRESS_SUBSCRIBERS) {
- // subscribe to MIDI port
- if (snd_seq_connect_to(seq_handle, my_port, seq_client, seq_port) < 0) {
- error("Can't subscribe to MIDI port (%d:%d) see README for help", seq_client, seq_port);
+ if (seq_client != SND_SEQ_ADDRESS_SUBSCRIBERS) {
+ // Subscribe to MIDI port. Prefer one that doesn't already have
+ // any connections, unless we've forced a port number already.
+ if (seq_port == -1) {
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+
+ snd_seq_client_info_alloca(&cinfo);
+ snd_seq_port_info_alloca(&pinfo);
+
+ snd_seq_get_any_client_info(seq_handle, seq_client, cinfo);
+
+ int first_port = -1;
+ int found_port = -1;
+
+ snd_seq_port_info_set_client(pinfo, seq_client);
+ snd_seq_port_info_set_port(pinfo, -1);
+ while (found_port == -1 && snd_seq_query_next_port(seq_handle, pinfo) >= 0) {
+ if (check_permission(pinfo)) {
+ if (first_port == -1)
+ first_port = snd_seq_port_info_get_port(pinfo);
+ if (found_port == -1 && snd_seq_port_info_get_write_use(pinfo) == 0)
+ found_port = snd_seq_port_info_get_port(pinfo);
+ }
+ }
+
+ if (found_port == -1) {
+ // Should we abort here? For now, use the first
+ // available port.
+ seq_port = first_port;
+ warning("MidiDriver_ALSA: All ports on client %d (%s) are already in use", seq_client, snd_seq_client_info_get_name(cinfo));
+ } else {
+ seq_port = found_port;
}
- }
- } else {
- int defaultPorts[] = {
- 65, 0,
- 17, 0
- };
- int i;
-
- for (i = 0; i < ARRAYSIZE(defaultPorts); i += 2) {
- seq_client = defaultPorts[i];
- seq_port = defaultPorts[i + 1];
- if (snd_seq_connect_to(seq_handle, my_port, seq_client, seq_port) >= 0)
- break;
}
- if (i >= ARRAYSIZE(defaultPorts))
- error("Can't subscribe to MIDI port (65:0) or (17:0)");
+ if (snd_seq_connect_to(seq_handle, my_port, seq_client, seq_port) < 0) {
+ error("Can't subscribe to MIDI port (%d:%d) see README for help", seq_client, seq_port);
+ }
}
printf("Connected to Alsa sequencer client [%d:%d]\n", seq_client, seq_port);
@@ -150,10 +167,13 @@ int MidiDriver_ALSA::open() {
}
void MidiDriver_ALSA::close() {
- _isOpen = false;
- MidiDriver_MPU401::close();
- if (seq_handle)
- snd_seq_close(seq_handle);
+ if (_isOpen) {
+ _isOpen = false;
+ MidiDriver_MPU401::close();
+ if (seq_handle)
+ snd_seq_close(seq_handle);
+ } else
+ warning("MidiDriver_ALSA: Closing the driver before opening it");
}
void MidiDriver_ALSA::send(uint32 b) {
@@ -227,24 +247,6 @@ void MidiDriver_ALSA::sysEx(const byte *msg, uint16 length) {
send_event(1);
}
-int MidiDriver_ALSA::parse_addr(const char *arg, int *client, int *port) {
- const char *p;
-
- if (isdigit(*arg)) {
- if ((p = strpbrk(arg, ADDR_DELIM)) == NULL)
- return -1;
- *client = atoi(arg);
- *port = atoi(p + 1);
- } else {
- if (*arg == 's' || *arg == 'S') {
- *client = SND_SEQ_ADDRESS_SUBSCRIBERS;
- *port = 0;
- } else
- return -1;
- }
- return 0;
-}
-
void MidiDriver_ALSA::send_event(int do_flush) {
snd_seq_ev_set_direct(&ev);
snd_seq_ev_set_source(&ev, my_port);
@@ -258,6 +260,37 @@ void MidiDriver_ALSA::send_event(int do_flush) {
// Plugin interface
+class AlsaDevice {
+public:
+ AlsaDevice(Common::String name, MusicType mt, int client);
+ Common::String getName();
+ MusicType getType();
+ int getClient();
+
+private:
+ Common::String _name;
+ MusicType _type;
+ int _client;
+};
+
+typedef Common::List<AlsaDevice> AlsaDevices;
+
+AlsaDevice::AlsaDevice(Common::String name, MusicType mt, int client)
+ : _name(name), _type(mt), _client(client) {
+}
+
+Common::String AlsaDevice::getName() {
+ return _name;
+}
+
+MusicType AlsaDevice::getType() {
+ return _type;
+}
+
+int AlsaDevice::getClient() {
+ return _client;
+}
+
class AlsaMusicPlugin : public MusicPluginObject {
public:
const char *getName() const {
@@ -268,26 +301,18 @@ public:
return "alsa";
}
+ AlsaDevices getAlsaDevices() const;
MusicDevices getDevices() const;
- Common::Error createInstance(MidiDriver **mididriver) const;
-};
-
-#define perm_ok(pinfo,bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
+ Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
-static int check_permission(snd_seq_port_info_t *pinfo)
-{
- if (perm_ok(pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) {
- if (!(snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_NO_EXPORT))
- return 1;
- }
- return 0;
-}
-
-MusicDevices AlsaMusicPlugin::getDevices() const {
- MusicDevices devices;
+private:
+ static int parse_addr(const char *arg, int *client, int *port);
+};
- snd_seq_t *seq;
- if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0)
+AlsaDevices AlsaMusicPlugin::getAlsaDevices() const {
+ AlsaDevices devices;
+ snd_seq_t *seq_handle;
+ if (my_snd_seq_open(&seq_handle) < 0)
return devices; // can't open sequencer
snd_seq_client_info_t *cinfo;
@@ -295,39 +320,145 @@ MusicDevices AlsaMusicPlugin::getDevices() const {
snd_seq_port_info_t *pinfo;
snd_seq_port_info_alloca(&pinfo);
snd_seq_client_info_set_client(cinfo, -1);
- while (snd_seq_query_next_client(seq, cinfo) >= 0) {
+ while (snd_seq_query_next_client(seq_handle, cinfo) >= 0) {
bool found_valid_port = false;
/* reset query info */
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
snd_seq_port_info_set_port(pinfo, -1);
- while (!found_valid_port && snd_seq_query_next_port(seq, pinfo) >= 0) {
+ while (!found_valid_port && snd_seq_query_next_port(seq_handle, pinfo) >= 0) {
if (check_permission(pinfo)) {
found_valid_port = true;
- // TODO: Return a different music type depending on the configuration
- devices.push_back(MusicDevice(this, snd_seq_client_info_get_name(cinfo), MT_GM));
- //snd_seq_client_info_get_client(cinfo) : snd_seq_port_info_get_port(pinfo)
+
+ const char *name = snd_seq_client_info_get_name(cinfo);
+ // TODO: Can we figure out the appropriate music type?
+ MusicType type = MT_GM;
+ int client = snd_seq_client_info_get_client(cinfo);
+ devices.push_back(AlsaDevice(name, type, client));
}
}
}
- snd_seq_close(seq);
+ snd_seq_close(seq_handle);
return devices;
}
-Common::Error AlsaMusicPlugin::createInstance(MidiDriver **mididriver) const {
- *mididriver = new MidiDriver_ALSA();
+MusicDevices AlsaMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ AlsaDevices::iterator d;
- return Common::kNoError;
+ AlsaDevices alsaDevices = getAlsaDevices();
+
+ // Since the default behaviour is to use the first device in the list,
+ // try to put something sensible there. We used to have 17:0 and 65:0
+ // as defaults.
+
+ for (d = alsaDevices.begin(); d != alsaDevices.end();) {
+ const int client = d->getClient();
+
+ if (client == 17 || client == 65) {
+ devices.push_back(MusicDevice(this, d->getName(), d->getType()));
+ d = alsaDevices.erase(d);
+ } else {
+ ++d;
+ }
+ }
+
+ // 128:0 is probably TiMidity, or something like that, so that's
+ // probably a good second choice.
+
+ for (d = alsaDevices.begin(); d != alsaDevices.end();) {
+ if (d->getClient() == 128) {
+ devices.push_back(MusicDevice(this, d->getName(), d->getType()));
+ d = alsaDevices.erase(d);
+ } else {
+ ++d;
+ }
+ }
+
+ // Add the remaining devices in the order they were found.
+
+ for (d = alsaDevices.begin(); d != alsaDevices.end(); ++d)
+ devices.push_back(MusicDevice(this, d->getName(), d->getType()));
+
+ return devices;
}
-MidiDriver *MidiDriver_ALSA_create() {
- MidiDriver *mididriver;
+Common::Error AlsaMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle dev) const {
+ bool found = false;
+ int seq_client, seq_port;
+
+ const char *var = NULL;
+
+ // TODO: Upgrade from old alsa_port setting. This probably isn't the
+ // right place to do that, though.
- AlsaMusicPlugin p;
- p.createInstance(&mididriver);
+ if (ConfMan.hasKey("alsa_port")) {
+ warning("AlsaMusicPlugin: Found old 'alsa_port' setting, which will be ignored");
+ }
+
+ // The SCUMMVM_PORT environment variable can still be used to override
+ // any config setting.
- return mididriver;
+ var = getenv("SCUMMVM_PORT");
+ if (var) {
+ warning("AlsaMusicPlugin: SCUMMVM_PORT environment variable overrides config settings");
+ if (parse_addr(var, &seq_client, &seq_port) >= 0) {
+ found = true;
+ } else {
+ warning("AlsaMusicPlugin: Invalid port %s, using config settings instead", var);
+ }
+ }
+
+ // Try to match the setting to an available ALSA device.
+
+ if (!found && dev) {
+ AlsaDevices alsaDevices = getAlsaDevices();
+
+ for (AlsaDevices::iterator d = alsaDevices.begin(); d != alsaDevices.end(); ++d) {
+ MusicDevice device(this, d->getName(), d->getType());
+
+ if (device.getCompleteId().equals(MidiDriver::getDeviceString(dev, MidiDriver::kDeviceId))) {
+ found = true;
+ seq_client = d->getClient();
+ seq_port = -1;
+ break;
+ }
+ }
+ }
+
+ // Still nothing? Try a sensible default.
+
+ if (!found) {
+ // TODO: What's a sensible default anyway? And exactly when do
+ // we get to this case?
+
+ warning("AlsaMusicPlugin: Using 17:0 as default ALSA port");
+ seq_client = 17;
+ seq_port = 0;
+ }
+
+ *mididriver = new MidiDriver_ALSA(seq_client, seq_port);
+
+ return Common::kNoError;
+}
+
+int AlsaMusicPlugin::parse_addr(const char *arg, int *client, int *port) {
+ const char *p;
+
+ if (isdigit(*arg)) {
+ if ((p = strpbrk(arg, ADDR_DELIM)) == NULL)
+ return -1;
+ *client = atoi(arg);
+ *port = atoi(p + 1);
+ } else {
+ if (*arg == 's' || *arg == 'S') {
+ *client = SND_SEQ_ADDRESS_SUBSCRIBERS;
+ *port = 0;
+ } else
+ return -1;
+ }
+ return 0;
}
//#if PLUGIN_ENABLED_DYNAMIC(ALSA)