int deviceInfoIterator(UINT32 deviceID, snd_pcm_info_t* pcminfo,
                       snd_ctl_card_info_t* cardinfo, void* userData) {
    char buffer[300];
    ALSA_AudioDeviceDescription* desc = (ALSA_AudioDeviceDescription*)userData;
#ifdef ALSA_PCM_USE_PLUGHW
    int usePlugHw = 1;
#else
    int usePlugHw = 0;
#endif

    initAlsaSupport();
    if (desc->index == 0) {
        // we found the device with correct index
        *(desc->maxSimultaneousLines) = needEnumerateSubdevices(ALSA_PCM) ?
                1 : snd_pcm_info_get_subdevices_count(pcminfo);
        *desc->deviceID = deviceID;
        buffer[0]=' '; buffer[1]='[';
        // buffer[300] is enough to store the actual device string w/o overrun
        getDeviceStringFromDeviceID(&buffer[2], deviceID, usePlugHw, ALSA_PCM);
        strncat(buffer, "]", sizeof(buffer) - strlen(buffer) - 1);
        strncpy(desc->name,
                (cardinfo != NULL)
                    ? snd_ctl_card_info_get_id(cardinfo)
                    : snd_pcm_info_get_id(pcminfo),
                desc->strLen - strlen(buffer));
        strncat(desc->name, buffer, desc->strLen - strlen(desc->name));
        strncpy(desc->vendor, "ALSA (http://www.alsa-project.org)", desc->strLen);
        strncpy(desc->description,
                (cardinfo != NULL)
                    ? snd_ctl_card_info_get_name(cardinfo)
                    : snd_pcm_info_get_name(pcminfo),
                desc->strLen);
        strncat(desc->description, ", ", desc->strLen - strlen(desc->description));
        strncat(desc->description, snd_pcm_info_get_id(pcminfo), desc->strLen - strlen(desc->description));
        strncat(desc->description, ", ", desc->strLen - strlen(desc->description));
        strncat(desc->description, snd_pcm_info_get_name(pcminfo), desc->strLen - strlen(desc->description));
        getALSAVersion(desc->version, desc->strLen);
        TRACE4("Returning %s, %s, %s, %s\n", desc->name, desc->vendor, desc->description, desc->version);
        return FALSE; // do not continue iteration
    }
    desc->index--;
    return TRUE;
}
// for each ALSA device, call iterator. userData is passed to the iterator
// returns total number of iterations
int iteratePCMDevices(DeviceIteratorPtr iterator, void* userData) {
    int count = 0;
    int subdeviceCount;
    int card, dev, subDev;
    char devname[16];
    int err;
    snd_ctl_t *handle;
    snd_pcm_t *pcm;
    snd_pcm_info_t* pcminfo;
    snd_ctl_card_info_t *cardinfo, *defcardinfo = NULL;
    UINT32 deviceID;
    int doContinue = TRUE;

    snd_pcm_info_malloc(&pcminfo);
    snd_ctl_card_info_malloc(&cardinfo);

    // 1st try "default" device
    err = snd_pcm_open(&pcm, ALSA_DEFAULT_DEVICE_NAME,
                       SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
    if (err < 0) {
        // try with the other direction
        err = snd_pcm_open(&pcm, ALSA_DEFAULT_DEVICE_NAME,
                           SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
    }
    if (err < 0) {
        ERROR1("ERROR: snd_pcm_open (\"default\"): %s\n", snd_strerror(err));
    } else {
        err = snd_pcm_info(pcm, pcminfo);
        snd_pcm_close(pcm);
        if (err < 0) {
            ERROR1("ERROR: snd_pcm_info (\"default\"): %s\n",
                    snd_strerror(err));
        } else {
            // try to get card info
            card = snd_pcm_info_get_card(pcminfo);
            if (card >= 0) {
                sprintf(devname, ALSA_HARDWARE_CARD, card);
                if (snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK) >= 0) {
                    if (snd_ctl_card_info(handle, cardinfo) >= 0) {
                        defcardinfo = cardinfo;
                    }
                    snd_ctl_close(handle);
                }
            }
            // call callback function for the device
            if (iterator != NULL) {
                doContinue = (*iterator)(ALSA_DEFAULT_DEVICE_ID, pcminfo,
                                         defcardinfo, userData);
            }
            count++;
        }
    }

    // iterate cards
    card = -1;
    while (doContinue) {
        if (snd_card_next(&card) < 0) {
            break;
        }
        if (card < 0) {
            break;
        }
        sprintf(devname, ALSA_HARDWARE_CARD, card);
        TRACE1("Opening alsa device \"%s\"...\n", devname);
        err = snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK);
        if (err < 0) {
            ERROR2("ERROR: snd_ctl_open, card=%d: %s\n",
                    card, snd_strerror(err));
        } else {
            err = snd_ctl_card_info(handle, cardinfo);
            if (err < 0) {
                ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n",
                        card, snd_strerror(err));
            } else {
                dev = -1;
                while (doContinue) {
                    if (snd_ctl_pcm_next_device(handle, &dev) < 0) {
                        ERROR0("snd_ctl_pcm_next_device\n");
                    }
                    if (dev < 0) {
                        break;
                    }
                    snd_pcm_info_set_device(pcminfo, dev);
                    snd_pcm_info_set_subdevice(pcminfo, 0);
                    snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
                    err = snd_ctl_pcm_info(handle, pcminfo);
                    if (err == -ENOENT) {
                        // try with the other direction
                        snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE);
                        err = snd_ctl_pcm_info(handle, pcminfo);
                    }
                    if (err < 0) {
                        if (err != -ENOENT) {
                            ERROR2("ERROR: snd_ctl_pcm_info, card=%d: %s",
                                    card, snd_strerror(err));
                        }
                    } else {
                        subdeviceCount = needEnumerateSubdevices(ALSA_PCM) ?
                            snd_pcm_info_get_subdevices_count(pcminfo) : 1;
                        if (iterator!=NULL) {
                            for (subDev = 0; subDev < subdeviceCount; subDev++) {
                                deviceID = encodeDeviceID(card, dev, subDev);
                                doContinue = (*iterator)(deviceID, pcminfo,
                                                         cardinfo, userData);
                                count++;
                                if (!doContinue) {
                                    break;
                                }
                            }
                        } else {
                            count += subdeviceCount;
                        }
                    }
                } // of while(doContinue)
            }
            snd_ctl_close(handle);
        }
    }
    snd_ctl_card_info_free(cardinfo);
    snd_pcm_info_free(pcminfo);
    return count;
}
// for each ALSA device, call iterator. userData is passed to the iterator
// returns total number of iterations
static int iterateRawmidiDevices(snd_rawmidi_stream_t direction,
                                 DeviceIteratorPtr iterator,
                                 void* userData) {
    int count = 0;
    int subdeviceCount;
    int card, dev, subDev;
    char devname[16];
    int err;
    snd_ctl_t *handle;
    snd_rawmidi_t *rawmidi;
    snd_rawmidi_info_t *rawmidi_info;
    snd_ctl_card_info_t *card_info, *defcardinfo = NULL;
    UINT32 deviceID;
    int doContinue = TRUE;

    snd_rawmidi_info_malloc(&rawmidi_info);
    snd_ctl_card_info_malloc(&card_info);

    // 1st try "default" device
    if (direction == SND_RAWMIDI_STREAM_INPUT) {
        err = snd_rawmidi_open(&rawmidi, NULL, ALSA_DEFAULT_DEVICE_NAME,
                               SND_RAWMIDI_NONBLOCK);
    } else if (direction == SND_RAWMIDI_STREAM_OUTPUT) {
        err = snd_rawmidi_open(NULL, &rawmidi, ALSA_DEFAULT_DEVICE_NAME,
                               SND_RAWMIDI_NONBLOCK);
    } else {
        ERROR0("ERROR: iterateRawmidiDevices(): direction is neither"
               " SND_RAWMIDI_STREAM_INPUT nor SND_RAWMIDI_STREAM_OUTPUT\n");
        err = MIDI_INVALID_ARGUMENT;
    }
    if (err < 0) {
        ERROR1("ERROR: snd_rawmidi_open (\"default\"): %s\n",
               snd_strerror(err));
    } else {
        err = snd_rawmidi_info(rawmidi, rawmidi_info);

        snd_rawmidi_close(rawmidi);
        if (err < 0) {
            ERROR1("ERROR: snd_rawmidi_info (\"default\"): %s\n",
                    snd_strerror(err));
        } else {
            // try to get card info
            card = snd_rawmidi_info_get_card(rawmidi_info);
            if (card >= 0) {
                sprintf(devname, ALSA_HARDWARE_CARD, card);
                if (snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK) >= 0) {
                    if (snd_ctl_card_info(handle, card_info) >= 0) {
                        defcardinfo = card_info;
                    }
                    snd_ctl_close(handle);
                }
            }
            // call calback function for the device
            if (iterator != NULL) {
                doContinue = (*iterator)(ALSA_DEFAULT_DEVICE_ID, rawmidi_info,
                                         defcardinfo, userData);
            }
            count++;
        }
    }

    // iterate cards
    card = -1;
    TRACE0("testing for cards...\n");
    if (snd_card_next(&card) >= 0) {
        TRACE1("Found card %d\n", card);
        while (doContinue && (card >= 0)) {
            sprintf(devname, ALSA_HARDWARE_CARD, card);
            TRACE1("Opening control for alsa rawmidi device \"%s\"...\n", devname);
            err = snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK);
            if (err < 0) {
                ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", card, snd_strerror(err));
            } else {
                TRACE0("snd_ctl_open() SUCCESS\n");
                err = snd_ctl_card_info(handle, card_info);
                if (err < 0) {
                    ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n", card, snd_strerror(err));
                } else {
                    TRACE0("snd_ctl_card_info() SUCCESS\n");
                    dev = -1;
                    while (doContinue) {
                        if (snd_ctl_rawmidi_next_device(handle, &dev) < 0) {
                            ERROR0("snd_ctl_rawmidi_next_device\n");
                        }
                        TRACE0("snd_ctl_rawmidi_next_device() SUCCESS\n");
                        if (dev < 0) {
                            break;
                        }
                        snd_rawmidi_info_set_device(rawmidi_info, dev);
                        snd_rawmidi_info_set_subdevice(rawmidi_info, 0);
                        snd_rawmidi_info_set_stream(rawmidi_info, direction);
                        err = snd_ctl_rawmidi_info(handle, rawmidi_info);
                        TRACE0("after snd_ctl_rawmidi_info()\n");
                        if (err < 0) {
                            if (err != -ENOENT) {
                                ERROR2("ERROR: snd_ctl_rawmidi_info, card=%d: %s", card, snd_strerror(err));
                            }
                        } else {
                            TRACE0("snd_ctl_rawmidi_info() SUCCESS\n");
                            subdeviceCount = needEnumerateSubdevices(ALSA_RAWMIDI)
                                ? snd_rawmidi_info_get_subdevices_count(rawmidi_info)
                                : 1;
                            if (iterator!=NULL) {
                                for (subDev = 0; subDev < subdeviceCount; subDev++) {
                                    TRACE3("  Iterating %d,%d,%d\n", card, dev, subDev);
                                    deviceID = encodeDeviceID(card, dev, subDev);
                                    doContinue = (*iterator)(deviceID, rawmidi_info,
                                                             card_info, userData);
                                    count++;
                                    TRACE0("returned from iterator\n");
                                    if (!doContinue) {
                                        break;
                                    }
                                }
                            } else {
                                count += subdeviceCount;
                            }
                        }
                    } // of while(doContinue)
                }
                snd_ctl_close(handle);
            }
            if (snd_card_next(&card) < 0) {
                break;
            }
        }
    } else {
        ERROR0("No cards found!\n");
    }
    snd_ctl_card_info_free(card_info);
    snd_rawmidi_info_free(rawmidi_info);
    return count;
}