1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #define USE_ERROR
  27 #define USE_TRACE
  28 
  29 #include "PLATFORM_API_LinuxOS_ALSA_MidiUtils.h"
  30 #include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h"
  31 #include <string.h>
  32 #include <sys/time.h>
  33 
  34 static INT64 getTimeInMicroseconds() {
  35     struct timeval tv;
  36 
  37     gettimeofday(&tv, NULL);
  38     return (tv.tv_sec * 1000000UL) + tv.tv_usec;
  39 }
  40 
  41 
  42 const char* getErrorStr(INT32 err) {
  43         return snd_strerror((int) err);
  44 }
  45 
  46 
  47 
  48 // callback for iteration through devices
  49 // returns TRUE if iteration should continue
  50 typedef int (*DeviceIteratorPtr)(UINT32 deviceID,
  51                                  snd_rawmidi_info_t* rawmidi_info,
  52                                  snd_ctl_card_info_t* cardinfo,
  53                                  void *userData);
  54 
  55 // for each ALSA device, call iterator. userData is passed to the iterator
  56 // returns total number of iterations
  57 static int iterateRawmidiDevices(snd_rawmidi_stream_t direction,
  58                                  DeviceIteratorPtr iterator,
  59                                  void* userData) {
  60     int count = 0;
  61     int subdeviceCount;
  62     int card, dev, subDev;
  63     char devname[16];
  64     int err;
  65     snd_ctl_t *handle;
  66     snd_rawmidi_t *rawmidi;
  67     snd_rawmidi_info_t *rawmidi_info;
  68     snd_ctl_card_info_t *card_info, *defcardinfo = NULL;
  69     UINT32 deviceID;
  70     int doContinue = TRUE;
  71 
  72     snd_rawmidi_info_malloc(&rawmidi_info);
  73     snd_ctl_card_info_malloc(&card_info);
  74 
  75     // 1st try "default" device
  76     if (direction == SND_RAWMIDI_STREAM_INPUT) {
  77         err = snd_rawmidi_open(&rawmidi, NULL, ALSA_DEFAULT_DEVICE_NAME,
  78                                SND_RAWMIDI_NONBLOCK);
  79     } else if (direction == SND_RAWMIDI_STREAM_OUTPUT) {
  80         err = snd_rawmidi_open(NULL, &rawmidi, ALSA_DEFAULT_DEVICE_NAME,
  81                                SND_RAWMIDI_NONBLOCK);
  82     } else {
  83         ERROR0("ERROR: iterateRawmidiDevices(): direction is neither"
  84                " SND_RAWMIDI_STREAM_INPUT nor SND_RAWMIDI_STREAM_OUTPUT\n");
  85         err = MIDI_INVALID_ARGUMENT;
  86     }
  87     if (err < 0) {
  88         ERROR1("ERROR: snd_rawmidi_open (\"default\"): %s\n",
  89                snd_strerror(err));
  90     } else {
  91         err = snd_rawmidi_info(rawmidi, rawmidi_info);
  92 
  93         snd_rawmidi_close(rawmidi);
  94         if (err < 0) {
  95             ERROR1("ERROR: snd_rawmidi_info (\"default\"): %s\n",
  96                     snd_strerror(err));
  97         } else {
  98             // try to get card info
  99             card = snd_rawmidi_info_get_card(rawmidi_info);
 100             if (card >= 0) {
 101                 sprintf(devname, ALSA_HARDWARE_CARD, card);
 102                 if (snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK) >= 0) {
 103                     if (snd_ctl_card_info(handle, card_info) >= 0) {
 104                         defcardinfo = card_info;
 105                     }
 106                     snd_ctl_close(handle);
 107                 }
 108             }
 109             // call calback function for the device
 110             if (iterator != NULL) {
 111                 doContinue = (*iterator)(ALSA_DEFAULT_DEVICE_ID, rawmidi_info,
 112                                          defcardinfo, userData);
 113             }
 114             count++;
 115         }
 116     }
 117 
 118     // iterate cards
 119     card = -1;
 120     TRACE0("testing for cards...\n");
 121     if (snd_card_next(&card) >= 0) {
 122         TRACE1("Found card %d\n", card);
 123         while (doContinue && (card >= 0)) {
 124             sprintf(devname, ALSA_HARDWARE_CARD, card);
 125             TRACE1("Opening control for alsa rawmidi device \"%s\"...\n", devname);
 126             err = snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK);
 127             if (err < 0) {
 128                 ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", card, snd_strerror(err));
 129             } else {
 130                 TRACE0("snd_ctl_open() SUCCESS\n");
 131                 err = snd_ctl_card_info(handle, card_info);
 132                 if (err < 0) {
 133                     ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n", card, snd_strerror(err));
 134                 } else {
 135                     TRACE0("snd_ctl_card_info() SUCCESS\n");
 136                     dev = -1;
 137                     while (doContinue) {
 138                         if (snd_ctl_rawmidi_next_device(handle, &dev) < 0) {
 139                             ERROR0("snd_ctl_rawmidi_next_device\n");
 140                         }
 141                         TRACE0("snd_ctl_rawmidi_next_device() SUCCESS\n");
 142                         if (dev < 0) {
 143                             break;
 144                         }
 145                         snd_rawmidi_info_set_device(rawmidi_info, dev);
 146                         snd_rawmidi_info_set_subdevice(rawmidi_info, 0);
 147                         snd_rawmidi_info_set_stream(rawmidi_info, direction);
 148                         err = snd_ctl_rawmidi_info(handle, rawmidi_info);
 149                         TRACE0("after snd_ctl_rawmidi_info()\n");
 150                         if (err < 0) {
 151                             if (err != -ENOENT) {
 152                                 ERROR2("ERROR: snd_ctl_rawmidi_info, card=%d: %s", card, snd_strerror(err));
 153                             }
 154                         } else {
 155                             TRACE0("snd_ctl_rawmidi_info() SUCCESS\n");
 156                             subdeviceCount = needEnumerateSubdevices(ALSA_RAWMIDI)
 157                                 ? snd_rawmidi_info_get_subdevices_count(rawmidi_info)
 158                                 : 1;
 159                             if (iterator!=NULL) {
 160                                 for (subDev = 0; subDev < subdeviceCount; subDev++) {
 161                                     TRACE3("  Iterating %d,%d,%d\n", card, dev, subDev);
 162                                     deviceID = encodeDeviceID(card, dev, subDev);
 163                                     doContinue = (*iterator)(deviceID, rawmidi_info,
 164                                                              card_info, userData);
 165                                     count++;
 166                                     TRACE0("returned from iterator\n");
 167                                     if (!doContinue) {
 168                                         break;
 169                                     }
 170                                 }
 171                             } else {
 172                                 count += subdeviceCount;
 173                             }
 174                         }
 175                     } // of while(doContinue)
 176                 }
 177                 snd_ctl_close(handle);
 178             }
 179             if (snd_card_next(&card) < 0) {
 180                 break;
 181             }
 182         }
 183     } else {
 184         ERROR0("No cards found!\n");
 185     }
 186     snd_ctl_card_info_free(card_info);
 187     snd_rawmidi_info_free(rawmidi_info);
 188     return count;
 189 }
 190 
 191 
 192 
 193 int getMidiDeviceCount(snd_rawmidi_stream_t direction) {
 194     int deviceCount;
 195     TRACE0("> getMidiDeviceCount()\n");
 196     initAlsaSupport();
 197     deviceCount = iterateRawmidiDevices(direction, NULL, NULL);
 198     TRACE0("< getMidiDeviceCount()\n");
 199     return deviceCount;
 200 }
 201 
 202 
 203 
 204 /*
 205   userData is assumed to be a pointer to ALSA_MIDIDeviceDescription.
 206   ALSA_MIDIDeviceDescription->index has to be set to the index of the device
 207   we want to get information of before this method is called the first time via
 208   iterateRawmidiDevices(). On each call of this method,
 209   ALSA_MIDIDeviceDescription->index is decremented. If it is equal to zero,
 210   we have reached the desired device, so action is taken.
 211   So after successful completion of iterateRawmidiDevices(),
 212   ALSA_MIDIDeviceDescription->index is zero. If it isn't, this is an
 213   indication of an error.
 214 */
 215 static int deviceInfoIterator(UINT32 deviceID, snd_rawmidi_info_t *rawmidi_info,
 216                               snd_ctl_card_info_t *cardinfo, void *userData) {
 217     char buffer[300];
 218     ALSA_MIDIDeviceDescription* desc = (ALSA_MIDIDeviceDescription*)userData;
 219 #ifdef ALSA_MIDI_USE_PLUGHW
 220     int usePlugHw = 1;
 221 #else
 222     int usePlugHw = 0;
 223 #endif
 224 
 225     TRACE0("deviceInfoIterator\n");
 226     initAlsaSupport();
 227     if (desc->index == 0) {
 228         // we found the device with correct index
 229         desc->deviceID = deviceID;
 230 
 231         buffer[0]=' '; buffer[1]='[';
 232         // buffer[300] is enough to store the actual device string w/o overrun
 233         getDeviceStringFromDeviceID(&buffer[2], deviceID, usePlugHw, ALSA_RAWMIDI);
 234         strncat(buffer, "]", sizeof(buffer) - strlen(buffer) - 1);
 235         strncpy(desc->name,
 236                 (cardinfo != NULL)
 237                     ? snd_ctl_card_info_get_id(cardinfo)
 238                     : snd_rawmidi_info_get_id(rawmidi_info),
 239                 desc->strLen - strlen(buffer));
 240         strncat(desc->name, buffer, desc->strLen - strlen(desc->name));
 241         desc->description[0] = 0;
 242         if (cardinfo != NULL) {
 243             strncpy(desc->description, snd_ctl_card_info_get_name(cardinfo),
 244                     desc->strLen);
 245             strncat(desc->description, ", ",
 246                     desc->strLen - strlen(desc->description));
 247         }
 248         strncat(desc->description, snd_rawmidi_info_get_id(rawmidi_info),
 249                 desc->strLen - strlen(desc->description));
 250         strncat(desc->description, ", ", desc->strLen - strlen(desc->description));
 251         strncat(desc->description, snd_rawmidi_info_get_name(rawmidi_info),
 252                 desc->strLen - strlen(desc->description));
 253         TRACE2("Returning %s, %s\n", desc->name, desc->description);
 254         return FALSE; // do not continue iteration
 255     }
 256     desc->index--;
 257     return TRUE;
 258 }
 259 
 260 
 261 static int getMIDIDeviceDescriptionByIndex(snd_rawmidi_stream_t direction,
 262                                            ALSA_MIDIDeviceDescription* desc) {
 263     initAlsaSupport();
 264     TRACE1(" getMIDIDeviceDescriptionByIndex (index = %d)\n", desc->index);
 265     iterateRawmidiDevices(direction, &deviceInfoIterator, desc);
 266     return (desc->index == 0) ? MIDI_SUCCESS : MIDI_INVALID_DEVICEID;
 267 }
 268 
 269 
 270 
 271 int initMIDIDeviceDescription(ALSA_MIDIDeviceDescription* desc, int index) {
 272     int ret = MIDI_SUCCESS;
 273     desc->index = index;
 274     desc->strLen = 200;
 275     desc->name = (char*) calloc(desc->strLen + 1, 1);
 276     desc->description = (char*) calloc(desc->strLen + 1, 1);
 277     if (! desc->name ||
 278         ! desc->description) {
 279         ret = MIDI_OUT_OF_MEMORY;
 280     }
 281     return ret;
 282 }
 283 
 284 
 285 void freeMIDIDeviceDescription(ALSA_MIDIDeviceDescription* desc) {
 286     if (desc->name) {
 287         free(desc->name);
 288     }
 289     if (desc->description) {
 290         free(desc->description);
 291     }
 292 }
 293 
 294 
 295 int getMidiDeviceName(snd_rawmidi_stream_t direction, int index, char *name,
 296                       UINT32 nameLength) {
 297     ALSA_MIDIDeviceDescription desc;
 298     int ret;
 299 
 300     TRACE1("getMidiDeviceName: nameLength: %d\n", (int) nameLength);
 301     ret = initMIDIDeviceDescription(&desc, index);
 302     if (ret == MIDI_SUCCESS) {
 303         TRACE0("getMidiDeviceName: initMIDIDeviceDescription() SUCCESS\n");
 304         ret = getMIDIDeviceDescriptionByIndex(direction, &desc);
 305         if (ret == MIDI_SUCCESS) {
 306             TRACE1("getMidiDeviceName: desc.name: %s\n", desc.name);
 307             strncpy(name, desc.name, nameLength - 1);
 308             name[nameLength - 1] = 0;
 309         }
 310     }
 311     freeMIDIDeviceDescription(&desc);
 312     return ret;
 313 }
 314 
 315 
 316 int getMidiDeviceVendor(int index, char *name, UINT32 nameLength) {
 317     strncpy(name, ALSA_VENDOR, nameLength - 1);
 318     name[nameLength - 1] = 0;
 319     return MIDI_SUCCESS;
 320 }
 321 
 322 
 323 int getMidiDeviceDescription(snd_rawmidi_stream_t direction,
 324                              int index, char *name, UINT32 nameLength) {
 325     ALSA_MIDIDeviceDescription desc;
 326     int ret;
 327 
 328     ret = initMIDIDeviceDescription(&desc, index);
 329     if (ret == MIDI_SUCCESS) {
 330         ret = getMIDIDeviceDescriptionByIndex(direction, &desc);
 331         if (ret == MIDI_SUCCESS) {
 332             strncpy(name, desc.description, nameLength - 1);
 333             name[nameLength - 1] = 0;
 334         }
 335     }
 336     freeMIDIDeviceDescription(&desc);
 337     return ret;
 338 }
 339 
 340 
 341 int getMidiDeviceVersion(int index, char *name, UINT32 nameLength) {
 342     getALSAVersion(name, nameLength);
 343     return MIDI_SUCCESS;
 344 }
 345 
 346 
 347 static int getMidiDeviceID(snd_rawmidi_stream_t direction, int index,
 348                            UINT32* deviceID) {
 349     ALSA_MIDIDeviceDescription desc;
 350     int ret;
 351 
 352     ret = initMIDIDeviceDescription(&desc, index);
 353     if (ret == MIDI_SUCCESS) {
 354         ret = getMIDIDeviceDescriptionByIndex(direction, &desc);
 355         if (ret == MIDI_SUCCESS) {
 356             // TRACE1("getMidiDeviceName: desc.name: %s\n", desc.name);
 357             *deviceID = desc.deviceID;
 358         }
 359     }
 360     freeMIDIDeviceDescription(&desc);
 361     return ret;
 362 }
 363 
 364 
 365 /*
 366   direction has to be either SND_RAWMIDI_STREAM_INPUT or
 367   SND_RAWMIDI_STREAM_OUTPUT.
 368   Returns 0 on success. Otherwise, MIDI_OUT_OF_MEMORY, MIDI_INVALID_ARGUMENT
 369    or a negative ALSA error code is returned.
 370 */
 371 INT32 openMidiDevice(snd_rawmidi_stream_t direction, INT32 deviceIndex,
 372                      MidiDeviceHandle** handle) {
 373     snd_rawmidi_t* native_handle;
 374     snd_midi_event_t* event_parser = NULL;
 375     int err;
 376     UINT32 deviceID = 0;
 377     char devicename[100];
 378 #ifdef ALSA_MIDI_USE_PLUGHW
 379     int usePlugHw = 1;
 380 #else
 381     int usePlugHw = 0;
 382 #endif
 383 
 384     TRACE0("> openMidiDevice()\n");
 385 
 386     (*handle) = (MidiDeviceHandle*) calloc(sizeof(MidiDeviceHandle), 1);
 387     if (!(*handle)) {
 388         ERROR0("ERROR: openDevice: out of memory\n");
 389         return MIDI_OUT_OF_MEMORY;
 390     }
 391 
 392     // TODO: iterate to get dev ID from index
 393     err = getMidiDeviceID(direction, deviceIndex, &deviceID);
 394     TRACE1("  openMidiDevice(): deviceID: %d\n", (int) deviceID);
 395     getDeviceStringFromDeviceID(devicename, deviceID,
 396                                 usePlugHw, ALSA_RAWMIDI);
 397     TRACE1("  openMidiDevice(): deviceString: %s\n", devicename);
 398 
 399     // finally open the device
 400     if (direction == SND_RAWMIDI_STREAM_INPUT) {
 401         err = snd_rawmidi_open(&native_handle, NULL, devicename,
 402                                SND_RAWMIDI_NONBLOCK);
 403     } else if (direction == SND_RAWMIDI_STREAM_OUTPUT) {
 404         err = snd_rawmidi_open(NULL, &native_handle, devicename,
 405                                SND_RAWMIDI_NONBLOCK);
 406     } else {
 407         ERROR0("  ERROR: openMidiDevice(): direction is neither SND_RAWMIDI_STREAM_INPUT nor SND_RAWMIDI_STREAM_OUTPUT\n");
 408         err = MIDI_INVALID_ARGUMENT;
 409     }
 410     if (err < 0) {
 411         ERROR1("<  ERROR: openMidiDevice(): snd_rawmidi_open() returned %d\n", err);
 412         free(*handle);
 413         (*handle) = NULL;
 414         return err;
 415     }
 416     /* We opened with non-blocking behaviour to not get hung if the device
 417        is used by a different process. Writing, however, should
 418        be blocking. So we change it here. */
 419     if (direction == SND_RAWMIDI_STREAM_OUTPUT) {
 420         err = snd_rawmidi_nonblock(native_handle, 0);
 421         if (err < 0) {
 422             ERROR1("  ERROR: openMidiDevice(): snd_rawmidi_nonblock() returned %d\n", err);
 423             snd_rawmidi_close(native_handle);
 424             free(*handle);
 425             (*handle) = NULL;
 426             return err;
 427         }
 428     }
 429     if (direction == SND_RAWMIDI_STREAM_INPUT) {
 430         err = snd_midi_event_new(EVENT_PARSER_BUFSIZE, &event_parser);
 431         if (err < 0) {
 432             ERROR1("  ERROR: openMidiDevice(): snd_midi_event_new() returned %d\n", err);
 433             snd_rawmidi_close(native_handle);
 434             free(*handle);
 435             (*handle) = NULL;
 436             return err;
 437         }
 438     }
 439 
 440     (*handle)->deviceHandle = (void*) native_handle;
 441     (*handle)->startTime = getTimeInMicroseconds();
 442     (*handle)->platformData = event_parser;
 443     TRACE0("< openMidiDevice(): succeeded\n");
 444     return err;
 445 }
 446 
 447 
 448 
 449 INT32 closeMidiDevice(MidiDeviceHandle* handle) {
 450     int err;
 451 
 452     TRACE0("> closeMidiDevice()\n");
 453     if (!handle) {
 454         ERROR0("< ERROR: closeMidiDevice(): handle is NULL\n");
 455         return MIDI_INVALID_HANDLE;
 456     }
 457     if (!handle->deviceHandle) {
 458         ERROR0("< ERROR: closeMidiDevice(): native handle is NULL\n");
 459         return MIDI_INVALID_HANDLE;
 460     }
 461     err = snd_rawmidi_close((snd_rawmidi_t*) handle->deviceHandle);
 462     TRACE1("  snd_rawmidi_close() returns %d\n", err);
 463     if (handle->platformData) {
 464         snd_midi_event_free((snd_midi_event_t*) handle->platformData);
 465     }
 466     free(handle);
 467     TRACE0("< closeMidiDevice: succeeded\n");
 468     return err;
 469 }
 470 
 471 
 472 INT64 getMidiTimestamp(MidiDeviceHandle* handle) {
 473     if (!handle) {
 474         ERROR0("< ERROR: closeMidiDevice(): handle is NULL\n");
 475         return MIDI_INVALID_HANDLE;
 476     }
 477     return getTimeInMicroseconds() - handle->startTime;
 478 }
 479 
 480 
 481 /* end */