diff options
Diffstat (limited to 'backends/midi/alsa.cpp')
| -rw-r--r-- | backends/midi/alsa.cpp | 321 |
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) |
