ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

alsa.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 // Disable symbol overrides so that we can use system headers.
00024 #define FORBIDDEN_SYMBOL_ALLOW_ALL
00025 
00026 #include "common/scummsys.h"
00027 
00028 #if defined(USE_ALSA)
00029 
00030 #include "common/config-manager.h"
00031 #include "common/error.h"
00032 #include "common/textconsole.h"
00033 #include "common/util.h"
00034 #include "audio/musicplugin.h"
00035 #include "audio/mpu401.h"
00036 
00037 #include <alsa/asoundlib.h>
00038 
00039 /*
00040  *     ALSA sequencer driver
00041  * Mostly cut'n'pasted from Virtual Tiny Keyboard (vkeybd) by Takashi Iwai
00042  *                                      (you really rox, you know?)
00043  */
00044 
00045 #if SND_LIB_MAJOR >= 1 || SND_LIB_MINOR >= 6
00046 #define snd_seq_flush_output(x) snd_seq_drain_output(x)
00047 #define snd_seq_set_client_group(x,name)    /*nop */
00048 #define my_snd_seq_open(seqp) snd_seq_open(seqp, "hw", SND_SEQ_OPEN_DUPLEX, 0)
00049 #else
00050 /* SND_SEQ_OPEN_OUT causes oops on early version of ALSA */
00051 #define my_snd_seq_open(seqp) snd_seq_open(seqp, SND_SEQ_OPEN)
00052 #endif
00053 
00054 #define perm_ok(pinfo,bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
00055 
00056 static int check_permission(snd_seq_port_info_t *pinfo) {
00057     if (perm_ok(pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) {
00058         if (!(snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_NO_EXPORT))
00059             return 1;
00060     }
00061     return 0;
00062 }
00063 
00064 /*
00065  * parse address string
00066  */
00067 
00068 #define ADDR_DELIM      ".:"
00069 
00070 class MidiDriver_ALSA : public MidiDriver_MPU401 {
00071 public:
00072     MidiDriver_ALSA(int client, int port);
00073     int open();
00074     bool isOpen() const { return _isOpen; }
00075     void close();
00076     void send(uint32 b);
00077     void sysEx(const byte *msg, uint16 length);
00078 
00079 private:
00080     void send_event(int do_flush);
00081     bool _isOpen;
00082     snd_seq_event_t ev;
00083     snd_seq_t *seq_handle;
00084     int seq_client, seq_port;
00085     int my_client, my_port;
00086     // The volume controller value of the first MIDI channel
00087     int8 _channel0Volume;
00088 };
00089 
00090 MidiDriver_ALSA::MidiDriver_ALSA(int client, int port)
00091     : _isOpen(false), seq_handle(0), seq_client(client), seq_port(port), my_client(0), my_port(0), _channel0Volume(127) {
00092     memset(&ev, 0, sizeof(ev));
00093 }
00094 
00095 int MidiDriver_ALSA::open() {
00096     if (_isOpen)
00097         return MERR_ALREADY_OPEN;
00098     _isOpen = true;
00099 
00100     if (my_snd_seq_open(&seq_handle) < 0) {
00101         error("Can't open sequencer");
00102         return -1;
00103     }
00104 
00105     my_client = snd_seq_client_id(seq_handle);
00106     if (snd_seq_set_client_name(seq_handle, "RESIDUALVM") < 0) {
00107         error("Can't set sequencer client name");
00108     }
00109     snd_seq_set_client_group(seq_handle, "input");
00110 
00111     // According to http://www.alsa-project.org/~tiwai/alsa-subs.html
00112     // you can set read or write capabilities to allow other clients to
00113     // read or write the port. I don't think we need that, unless maybe
00114     // to be able to record the sound, but I can't get that to work even
00115     // with those capabilities.
00116 
00117     my_port = snd_seq_create_simple_port(seq_handle, "RESIDUALVM port 0", 0,
00118                                          SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
00119 
00120     if (my_port < 0) {
00121         snd_seq_close(seq_handle);
00122         error("Can't create port");
00123         return -1;
00124     }
00125 
00126     if (seq_client != SND_SEQ_ADDRESS_SUBSCRIBERS) {
00127         // Subscribe to MIDI port. Prefer one that doesn't already have
00128         // any connections, unless we've forced a port number already.
00129         if (seq_port == -1) {
00130             snd_seq_client_info_t *cinfo;
00131             snd_seq_port_info_t *pinfo;
00132 
00133             snd_seq_client_info_alloca(&cinfo);
00134             snd_seq_port_info_alloca(&pinfo);
00135 
00136             snd_seq_get_any_client_info(seq_handle, seq_client, cinfo);
00137 
00138             int first_port = -1;
00139             int found_port = -1;
00140 
00141             snd_seq_port_info_set_client(pinfo, seq_client);
00142             snd_seq_port_info_set_port(pinfo, -1);
00143             while (found_port == -1 && snd_seq_query_next_port(seq_handle, pinfo) >= 0) {
00144                 if (check_permission(pinfo)) {
00145                     if (first_port == -1)
00146                         first_port = snd_seq_port_info_get_port(pinfo);
00147                     if (found_port == -1 && snd_seq_port_info_get_write_use(pinfo) == 0)
00148                         found_port = snd_seq_port_info_get_port(pinfo);
00149                 }
00150             }
00151 
00152             if (found_port == -1) {
00153                 // Should we abort here? For now, use the first
00154                 // available port.
00155                 seq_port = first_port;
00156                 warning("MidiDriver_ALSA: All ports on client %d (%s) are already in use", seq_client, snd_seq_client_info_get_name(cinfo));
00157             } else {
00158                 seq_port = found_port;
00159             }
00160         }
00161 
00162         if (snd_seq_connect_to(seq_handle, my_port, seq_client, seq_port) < 0) {
00163             error("Can't subscribe to MIDI port (%d:%d) see README for help", seq_client, seq_port);
00164         }
00165     }
00166 
00167     printf("Connected to Alsa sequencer client [%d:%d]\n", seq_client, seq_port);
00168     printf("ALSA client initialized [%d:0]\n", my_client);
00169 
00170     return 0;
00171 }
00172 
00173 void MidiDriver_ALSA::close() {
00174     if (_isOpen) {
00175         _isOpen = false;
00176         MidiDriver_MPU401::close();
00177         if (seq_handle)
00178             snd_seq_close(seq_handle);
00179     } else
00180         warning("MidiDriver_ALSA: Closing the driver before opening it");
00181 }
00182 
00183 void MidiDriver_ALSA::send(uint32 b) {
00184     if (!_isOpen) {
00185         warning("MidiDriver_ALSA: Got event while not open");
00186         return;
00187     }
00188 
00189     unsigned int midiCmd[4];
00190     ev.type = SND_SEQ_EVENT_OSS;
00191 
00192     midiCmd[3] = (b & 0xFF000000) >> 24;
00193     midiCmd[2] = (b & 0x00FF0000) >> 16;
00194     midiCmd[1] = (b & 0x0000FF00) >> 8;
00195     midiCmd[0] = (b & 0x000000FF);
00196     ev.data.raw32.d[0] = midiCmd[0];
00197     ev.data.raw32.d[1] = midiCmd[1];
00198     ev.data.raw32.d[2] = midiCmd[2];
00199 
00200     unsigned char chanID = midiCmd[0] & 0x0F;
00201     switch (midiCmd[0] & 0xF0) {
00202     case 0x80:
00203         snd_seq_ev_set_noteoff(&ev, chanID, midiCmd[1], midiCmd[2]);
00204         send_event(1);
00205         break;
00206     case 0x90:
00207         snd_seq_ev_set_noteon(&ev, chanID, midiCmd[1], midiCmd[2]);
00208         send_event(1);
00209         break;
00210     case 0xA0:
00211         snd_seq_ev_set_keypress(&ev, chanID, midiCmd[1], midiCmd[2]);
00212         send_event(1);
00213         break;
00214     case 0xB0:
00215         /* is it this simple ? Wow... */
00216         snd_seq_ev_set_controller(&ev, chanID, midiCmd[1], midiCmd[2]);
00217 
00218         // We save the volume of the first MIDI channel here to utilize it in
00219         // our workaround for broken USB-MIDI cables.
00220         if (chanID == 0 && midiCmd[1] == 0x07)
00221             _channel0Volume = midiCmd[2];
00222 
00223         send_event(1);
00224         break;
00225     case 0xC0:
00226         snd_seq_ev_set_pgmchange(&ev, chanID, midiCmd[1]);
00227         send_event(0);
00228 
00229         // Send a volume change command to work around a firmware bug in common
00230         // USB-MIDI cables. If the first MIDI command in a USB packet is a
00231         // Cx or Dx command, the second command in the packet is dropped
00232         // somewhere.
00233         send(0x07B0 | (_channel0Volume << 16));
00234         break;
00235     case 0xD0:
00236         snd_seq_ev_set_chanpress(&ev, chanID, midiCmd[1]);
00237         send_event(1);
00238 
00239         // Send a volume change command to work around a firmware bug in common
00240         // USB-MIDI cables. If the first MIDI command in a USB packet is a
00241         // Cx or Dx command, the second command in the packet is dropped
00242         // somewhere.
00243         send(0x07B0 | (_channel0Volume << 16));
00244         break;
00245     case 0xE0: {
00246         // long theBend = ((((long)midiCmd[1] + (long)(midiCmd[2] << 7))) - 0x2000) / 4;
00247         // snd_seq_ev_set_pitchbend(&ev, chanID, theBend);
00248         long theBend = ((long)midiCmd[1] + (long)(midiCmd[2] << 7)) - 0x2000;
00249         snd_seq_ev_set_pitchbend(&ev, chanID, theBend);
00250         send_event(1);
00251         } break;
00252 
00253     default:
00254         warning("Unknown MIDI Command: %08x", (int)b);
00255         /* I don't know if this works but, well... */
00256         send_event(1);
00257         break;
00258     }
00259 }
00260 
00261 void MidiDriver_ALSA::sysEx(const byte *msg, uint16 length) {
00262     if (!_isOpen) {
00263         warning("MidiDriver_ALSA: Got SysEx while not open");
00264         return;
00265     }
00266 
00267     unsigned char buf[266];
00268 
00269     assert(length + 2 <= ARRAYSIZE(buf));
00270 
00271     // Add SysEx frame
00272     buf[0] = 0xF0;
00273     memcpy(buf + 1, msg, length);
00274     buf[length + 1] = 0xF7;
00275 
00276     // Send it
00277     snd_seq_ev_set_sysex(&ev, length + 2, &buf);
00278     send_event(1);
00279 }
00280 
00281 void MidiDriver_ALSA::send_event(int do_flush) {
00282     snd_seq_ev_set_direct(&ev);
00283     snd_seq_ev_set_source(&ev, my_port);
00284     snd_seq_ev_set_dest(&ev, seq_client, seq_port);
00285 
00286     snd_seq_event_output(seq_handle, &ev);
00287     if (do_flush)
00288         snd_seq_flush_output(seq_handle);
00289 }
00290 
00291 
00292 // Plugin interface
00293 
00294 class AlsaDevice {
00295 public:
00296     AlsaDevice(Common::String name, MusicType mt, int client);
00297     Common::String getName();
00298     MusicType getType();
00299     int getClient();
00300 
00301 private:
00302     Common::String _name;
00303     MusicType _type;
00304     int _client;
00305 };
00306 
00307 typedef Common::List<AlsaDevice> AlsaDevices;
00308 
00309 AlsaDevice::AlsaDevice(Common::String name, MusicType mt, int client)
00310     : _name(name), _type(mt), _client(client) {
00311     // Make sure we do not get any trailing spaces to avoid problems when
00312     // storing the name in the configuration file.
00313     _name.trim();
00314 }
00315 
00316 Common::String AlsaDevice::getName() {
00317     return _name;
00318 }
00319 
00320 MusicType AlsaDevice::getType() {
00321     return _type;
00322 }
00323 
00324 int AlsaDevice::getClient() {
00325     return _client;
00326 }
00327 
00328 class AlsaMusicPlugin : public MusicPluginObject {
00329 public:
00330     const char *getName() const {
00331         return "ALSA";
00332     }
00333 
00334     const char *getId() const {
00335         return "alsa";
00336     }
00337 
00338     AlsaDevices getAlsaDevices() const;
00339     MusicDevices getDevices() const;
00340     Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
00341 
00342 private:
00343     static int parse_addr(const char *arg, int *client, int *port);
00344 };
00345 
00346 AlsaDevices AlsaMusicPlugin::getAlsaDevices() const {
00347     AlsaDevices devices;
00348     snd_seq_t *seq_handle;
00349     if (my_snd_seq_open(&seq_handle) < 0)
00350         return devices; // can't open sequencer
00351 
00352     snd_seq_client_info_t *cinfo;
00353     snd_seq_client_info_alloca(&cinfo);
00354     snd_seq_port_info_t *pinfo;
00355     snd_seq_port_info_alloca(&pinfo);
00356     snd_seq_client_info_set_client(cinfo, -1);
00357     while (snd_seq_query_next_client(seq_handle, cinfo) >= 0) {
00358         bool found_valid_port = false;
00359 
00360         /* reset query info */
00361         snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
00362         snd_seq_port_info_set_port(pinfo, -1);
00363         while (!found_valid_port && snd_seq_query_next_port(seq_handle, pinfo) >= 0) {
00364             if (check_permission(pinfo)) {
00365                 found_valid_port = true;
00366 
00367                 const char *name = snd_seq_client_info_get_name(cinfo);
00368                 // TODO: Can we figure out the appropriate music type?
00369                 MusicType type = MT_GM;
00370                 int client = snd_seq_client_info_get_client(cinfo);
00371                 devices.push_back(AlsaDevice(name, type, client));
00372             }
00373         }
00374     }
00375     snd_seq_close(seq_handle);
00376 
00377     return devices;
00378 }
00379 
00380 MusicDevices AlsaMusicPlugin::getDevices() const {
00381     MusicDevices devices;
00382     AlsaDevices::iterator d;
00383 
00384     AlsaDevices alsaDevices = getAlsaDevices();
00385 
00386     // Since the default behavior is to use the first device in the list,
00387     // try to put something sensible there. We used to have 17:0 and 65:0
00388     // as defaults.
00389 
00390     for (d = alsaDevices.begin(); d != alsaDevices.end();) {
00391         const int client = d->getClient();
00392 
00393         if (client == 17 || client == 65) {
00394             devices.push_back(MusicDevice(this, d->getName(), d->getType()));
00395             d = alsaDevices.erase(d);
00396         } else {
00397             ++d;
00398         }
00399     }
00400 
00401     // 128:0 is probably TiMidity, or something like that, so that's
00402     // probably a good second choice.
00403 
00404     for (d = alsaDevices.begin(); d != alsaDevices.end();) {
00405         if (d->getClient() == 128) {
00406             devices.push_back(MusicDevice(this, d->getName(), d->getType()));
00407             d = alsaDevices.erase(d);
00408         } else {
00409             ++d;
00410         }
00411     }
00412 
00413     // Add the remaining devices in the order they were found.
00414 
00415     for (d = alsaDevices.begin(); d != alsaDevices.end(); ++d)
00416         devices.push_back(MusicDevice(this, d->getName(), d->getType()));
00417 
00418     return devices;
00419 }
00420 
00421 Common::Error AlsaMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle dev) const {
00422     bool found = false;
00423     int seq_client, seq_port;
00424 
00425     const char *var = NULL;
00426 
00427     // TODO: Upgrade from old alsa_port setting. This probably isn't the
00428     // right place to do that, though.
00429 
00430     if (ConfMan.hasKey("alsa_port")) {
00431         warning("AlsaMusicPlugin: Found old 'alsa_port' setting, which will be ignored");
00432     }
00433 
00434     // The SCUMMVM_PORT environment variable can still be used to override
00435     // any config setting.
00436 
00437     var = getenv("RESIDUALVM_PORT");
00438     if (var) {
00439         warning("AlsaMusicPlugin: RESIDUALVM_PORT environment variable overrides config settings");
00440         if (parse_addr(var, &seq_client, &seq_port) >= 0) {
00441             found = true;
00442         } else {
00443             warning("AlsaMusicPlugin: Invalid port %s, using config settings instead", var);
00444         }
00445     }
00446 
00447     // Try to match the setting to an available ALSA device.
00448 
00449     if (!found && dev) {
00450         AlsaDevices alsaDevices = getAlsaDevices();
00451 
00452         for (AlsaDevices::iterator d = alsaDevices.begin(); d != alsaDevices.end(); ++d) {
00453             MusicDevice device(this, d->getName(), d->getType());
00454 
00455             if (device.getCompleteId().equals(MidiDriver::getDeviceString(dev, MidiDriver::kDeviceId))) {
00456                 found = true;
00457                 seq_client = d->getClient();
00458                 seq_port = -1;
00459                 break;
00460             }
00461         }
00462     }
00463 
00464     // Still nothing? Try a sensible default.
00465 
00466     if (!found) {
00467         // TODO: What's a sensible default anyway? And exactly when do
00468         // we get to this case?
00469 
00470         warning("AlsaMusicPlugin: Using 17:0 as default ALSA port");
00471         seq_client = 17;
00472         seq_port = 0;
00473     }
00474 
00475     *mididriver = new MidiDriver_ALSA(seq_client, seq_port);
00476 
00477     return Common::kNoError;
00478 }
00479 
00480 int AlsaMusicPlugin::parse_addr(const char *arg, int *client, int *port) {
00481     const char *p;
00482 
00483     if (isdigit(*arg)) {
00484         if ((p = strpbrk(arg, ADDR_DELIM)) == NULL)
00485             return -1;
00486         *client = atoi(arg);
00487         *port = atoi(p + 1);
00488     } else {
00489         if (*arg == 's' || *arg == 'S') {
00490             *client = SND_SEQ_ADDRESS_SUBSCRIBERS;
00491             *port = 0;
00492         } else
00493             return -1;
00494     }
00495     return 0;
00496 }
00497 
00498 //#if PLUGIN_ENABLED_DYNAMIC(ALSA)
00499     //REGISTER_PLUGIN_DYNAMIC(ALSA, PLUGIN_TYPE_MUSIC, AlsaMusicPlugin);
00500 //#else
00501     REGISTER_PLUGIN_STATIC(ALSA, PLUGIN_TYPE_MUSIC, AlsaMusicPlugin);
00502 //#endif
00503 
00504 #endif


Generated on Sat Feb 16 2019 05:00:44 for ResidualVM by doxygen 1.7.1
curved edge   curved edge