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_BsdOS_ALSA_MidiUtils.h"
30#include "PLATFORM_API_BsdOS_ALSA_CommonUtils.h"
31#include <string.h>
32#include <sys/time.h>
33
34static INT64 getTimeInMicroseconds() {
35    struct timeval tv;
36
37    gettimeofday(&tv, NULL);
38    return (tv.tv_sec * 1000000UL) + tv.tv_usec;
39}
40
41
42const 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
50typedef 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
57static 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
193int 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*/
215static 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
261static 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
271int 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
285void 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
295int 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
316int 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
323int 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
341int getMidiDeviceVersion(int index, char *name, UINT32 nameLength) {
342    getALSAVersion(name, nameLength);
343    return MIDI_SUCCESS;
344}
345
346
347static 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*/
371INT32 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
449INT32 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
472INT64 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 */
482