ALSA audio & midi update

Hi Jules,

Here are two suggested updates for juce_linux_ALSA.cpp and juce_linux_Midi.cpp

The ALSA update is a follow to this thread: http://www.rawmaterialsoftware.com/viewtopic.php?f=5&t=8143

The main change is that it uses the pcm devices as listed by aplay -L instead of the hardware devices as listed by aplay -l : this allows it to use the ‘default’ alsa output , which is now generally configured to output to pulseaudio. A drawback is that aplay -L is often a quite long list with long names, and sometimes includes some “devices” that simply do not work, and that pulseaudio is generally a pain when you’re trying to switch between devices.

Another change is that devices are not opened during the enumeration. This allows it to list devices that are current in use.

I think it is also slightly more robust that the previous version, with better error reporting.

I have kept the possibility to list hardware soundcards instead of listing the pcm devices, with the ‘listOnlySoundcard’ argument. However my application has been out for a few weeks with only pcm devices enabled, I have got no complaints

The proposed update to juce_linux_Midi.cpp has too objectives:

  • in the current version, each time the midi device list is enumerated, an alsa midi client is created (snd_seq_open), the devices are enumerated and the client is closed (snd_seq_close). This causes other applications that monitor alsa clients and connections to consider that something has changed in the ALSA graph, each time. For example qjackctl prints a message each time my client polls the midi device list. My version reuses the same alsa handle to avoid that.
  • in the current version for each opened midi device, a new client is created (snd_seq_open), with a single connection to the target midi device. With my change, a single client is created, and it creates a port for each connection to a midi device. This is much nicer in qjackctl. There is also now a single thread for all midi inputs, instead of one thread per connection. In order to use the same alsa client handle when enumerating devices, and for actual connections to other devices, I had to make a singleton from the AlsaClient class. Maybe this is something that will make you frown (the singleton)

Feel free to do whatever you want with these modifications, you can use them, or ignore them if you do not find those changes relevant, or if you think they are too invasive (this is more than a small patch, especially for the midi code I had to shuffle a lot of the original code)

juce_linux_ALSA.cpp:

[code]/*

This file is part of the JUCE library - "Jules’ Utility Class Extensions"
Copyright 2004-11 by Raw Material Software Ltd.


JUCE can be redistributed and/or modified under the terms of the GNU General
Public License (Version 2), as published by the Free Software Foundation.
A copy of the license is included in the JUCE distribution, or can be found
online at www.gnu.org/licenses.

JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.


To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.rawmaterialsoftware.com/juce for more information.

==============================================================================
*/
#include
using std::cerr;
namespace
{
int alsa_verbose = 0;

#define IF_ALSA_VERBOSE(x) if (alsa_verbose) std::cerr << x << “\n”;
#define CHECKED_ALSA(x) (checked_alsa(x, #x, LINE, PRETTY_FUNCTION))

int checked_alsa(int err, const char *what, int lnum, const char *fname) 
{
    if (err < 0 && alsa_verbose) 
    {
        std::cerr << fname << ":" << lnum << " ALSA called failed: " << what << "; returned " << err << " (" << snd_strerror(err) << ")\n";
    }
    return err;
}

void getDeviceSampleRates (snd_pcm_t* handle, Array <int>& rates)
{
    const int ratesToTry[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 };

    snd_pcm_hw_params_t* hwParams;
    snd_pcm_hw_params_alloca (&hwParams);

    for (int i = 0; ratesToTry[i] != 0; ++i)
    {
        if (snd_pcm_hw_params_any (handle, hwParams) >= 0
             && snd_pcm_hw_params_test_rate (handle, hwParams, ratesToTry[i], 0) == 0)
        {
            rates.addIfNotAlreadyThere (ratesToTry[i]);
        }
    }
}

void getDeviceNumChannels (snd_pcm_t* handle, unsigned int* minChans, unsigned int* maxChans)
{
    snd_pcm_hw_params_t *params;
    snd_pcm_hw_params_alloca (&params);

    if (snd_pcm_hw_params_any (handle, params) >= 0)
    {
        snd_pcm_hw_params_get_channels_min (params, minChans);
        snd_pcm_hw_params_get_channels_max (params, maxChans);
        IF_ALSA_VERBOSE("getDeviceNumChannels: " << *minChans << " " << *maxChans);
        // some virtual devices (dmix for example) report 10000 channels , we have to clamp these values
        *maxChans = jmin(*maxChans, 32u);
        *minChans = jmin(*minChans, *maxChans);
    } else IF_ALSA_VERBOSE("getDeviceNumChannels failed");
}

void getDeviceProperties (const String& deviceID,
                          unsigned int& minChansOut,
                          unsigned int& maxChansOut,
                          unsigned int& minChansIn,
                          unsigned int& maxChansIn,
                          Array <int>& rates, 
                          bool testOutput=true, bool testInput=true)
{
    minChansOut = maxChansOut = minChansIn = maxChansIn = 0;

    if (deviceID.isEmpty())
        return;

    IF_ALSA_VERBOSE("getDeviceProperties(" << deviceID.toUTF8().getAddress() << ")");

    
    snd_pcm_info_t* info;
    snd_pcm_info_alloca (&info);
          
    if (testOutput) {
        snd_pcm_t* pcmHandle;
        if (CHECKED_ALSA(snd_pcm_open (&pcmHandle, deviceID.toUTF8().getAddress(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) >= 0)
        {
            getDeviceNumChannels (pcmHandle, &minChansOut, &maxChansOut);
            getDeviceSampleRates (pcmHandle, rates);
            
            snd_pcm_close (pcmHandle);
        }
    }
    if (testInput) {
        snd_pcm_t* pcmHandle;
        if (CHECKED_ALSA(snd_pcm_open (&pcmHandle, deviceID.toUTF8(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) >= 0))
        {
            getDeviceNumChannels (pcmHandle, &minChansIn, &maxChansIn);
            
            if (rates.size() == 0)
                getDeviceSampleRates (pcmHandle, rates);
            
            snd_pcm_close (pcmHandle);
        }
    }
}

//==============================================================================
class ALSADevice
{
String devid;
public:
#define failed(x) failed_(x, #x, LINE)
ALSADevice (const String& deviceID, bool forInput)
: handle (0),
bitDepth (16),
numChannelsRunning (0),
latency (0),
isInput (forInput),
isInterleaved (true)
{
devid = deviceID;
IF_ALSA_VERBOSE("\nnew ALSADevice, calling snd_pcm_open(" << deviceID.toUTF8().getAddress() << “, forInput=” << forInput << “)”);
int err = snd_pcm_open (&handle, deviceID.toUTF8(),
forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
SND_PCM_ASYNC);
if (err < 0) {
// try to improve the ALSA error message for the common cases
if (-err == EBUSY) {
error << “The device “” << deviceID << “” is busy (another application is using it).”;
} else if (-err == ENOENT) {
error << “The device “” << deviceID << “” is not available.”;
} else {
error << “Could not open " << (forInput ? “input” : “output”) << " device “” << deviceID << “”: " << snd_strerror(err) << " (” << err << “)”;
}
IF_ALSA_VERBOSE("snd_pcm_open failed; " << error);
}
}

~ALSADevice()
{
  if (handle != 0) 
  {
      snd_pcm_close(handle);
  }
}

void closeNow() { if (handle) { snd_pcm_close(handle); handle = 0; } }

bool setParameters (unsigned int sampleRate, int numChannels, int bufferSize)
{
    if (handle == 0)
        return false;

    IF_ALSA_VERBOSE("ALSADevice::setParameters(" << devid.toUTF8().getAddress() << ", " << sampleRate << ", " << numChannels << ", " << bufferSize << ")");

    snd_pcm_hw_params_t* hwParams;
    snd_pcm_hw_params_alloca (&hwParams);

    if (snd_pcm_hw_params_any (handle, hwParams) < 0) {
        /* this is the error message that aplay returns when an error happens here,
           it is a bit more explicit that "Invalid parameter" */
        error = "Broken configuration for this PCM: no configurations available";
        return false;
    }

    if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED) >= 0) // works better for plughw..
        isInterleaved = true;
    else
      if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_NONINTERLEAVED) >= 0)
        isInterleaved = false;
    else 
    {
        jassertfalse;
        return false;
    }

    enum { isFloatBit = 1 << 16, isLittleEndianBit = 1 << 17 };

    const int formatsToTry[] = { SND_PCM_FORMAT_FLOAT_LE,   32 | isFloatBit | isLittleEndianBit,
                                 SND_PCM_FORMAT_FLOAT_BE,   32 | isFloatBit,
                                 SND_PCM_FORMAT_S32_LE,     32 | isLittleEndianBit,
                                 SND_PCM_FORMAT_S32_BE,     32,
                                 SND_PCM_FORMAT_S24_3LE,    24 | isLittleEndianBit,
                                 SND_PCM_FORMAT_S24_3BE,    24,
                                 SND_PCM_FORMAT_S16_LE,     16 | isLittleEndianBit,
                                 SND_PCM_FORMAT_S16_BE,     16 };
    bitDepth = 0;

    for (int i = 0; i < numElementsInArray (formatsToTry); i += 2)
    {
        if (snd_pcm_hw_params_set_format (handle, hwParams, (_snd_pcm_format) formatsToTry [i]) >= 0)
        {
            bitDepth = formatsToTry [i + 1] & 255;
            const bool isFloat = (formatsToTry [i + 1] & isFloatBit) != 0;
            const bool isLittleEndian = (formatsToTry [i + 1] & isLittleEndianBit) != 0;
            converter = createConverter (isInput, bitDepth, isFloat, isLittleEndian, numChannels);

            IF_ALSA_VERBOSE("  ALSA format: bitDepth=" << bitDepth << ", isFloat=" << isFloat << ", isLittleEndian=" << isLittleEndian << ", numChannels=" << numChannels);
            break;
        }
    }

    if (bitDepth == 0)
    {
        error = "device doesn't support a compatible PCM format";
        DBG ("ALSA error: " + error + "\n");
        return false;
    }

    int dir = 0;
    unsigned int periods = 4;
    snd_pcm_uframes_t samplesPerPeriod = bufferSize;

    if (failed (snd_pcm_hw_params_set_rate_near (handle, hwParams, &sampleRate, 0))
        || failed (snd_pcm_hw_params_set_channels (handle, hwParams, numChannels))
        || failed (snd_pcm_hw_params_set_periods_near (handle, hwParams, &periods, &dir))
        || failed (snd_pcm_hw_params_set_period_size_near (handle, hwParams, &samplesPerPeriod, &dir))
        || failed (snd_pcm_hw_params (handle, hwParams)))
    {
        return false;
    }

    snd_pcm_uframes_t frames = 0;

    if (failed (snd_pcm_hw_params_get_period_size (hwParams, &frames, &dir))
         || failed (snd_pcm_hw_params_get_periods (hwParams, &periods, &dir)))
        latency = 0;
    else
        latency = frames * (periods - 1); // (this is the method JACK uses to guess the latency..)

    IF_ALSA_VERBOSE("frames=" << frames << ", periods=" << periods << ", samplesPerPeriod=" << samplesPerPeriod);

    snd_pcm_sw_params_t* swParams;
    snd_pcm_sw_params_alloca (&swParams);
    snd_pcm_uframes_t boundary;

    if (failed (snd_pcm_sw_params_current (handle, swParams))
        || failed (snd_pcm_sw_params_get_boundary (swParams, &boundary))
        || failed (snd_pcm_sw_params_set_silence_threshold (handle, swParams, 0))
        || failed (snd_pcm_sw_params_set_silence_size (handle, swParams, boundary))
        || failed (snd_pcm_sw_params_set_start_threshold (handle, swParams, samplesPerPeriod))
        || failed (snd_pcm_sw_params_set_stop_threshold (handle, swParams, boundary))
        || failed (snd_pcm_sw_params (handle, swParams)))
    {
        return false;
    }

    if (alsa_verbose) {
      // enable this to dump the config of the devices that get opened
      snd_output_t* out;
      snd_output_stdio_attach (&out, stderr, 0);
      snd_pcm_hw_params_dump (hwParams, out);
      snd_pcm_sw_params_dump (swParams, out);
    }

    numChannelsRunning = numChannels;
    IF_ALSA_VERBOSE(" end of setParameters, numChannelsRunning=" << numChannelsRunning);
    return true;
}

//==============================================================================
bool writeToOutputDevice (AudioSampleBuffer& outputChannelBuffer, const int numSamples)
{
    jassert (numChannelsRunning <= outputChannelBuffer.getNumChannels());
    float** const data = outputChannelBuffer.getArrayOfChannels();
    snd_pcm_sframes_t numDone = 0;

    if (isInterleaved)
    {
        scratch.ensureSize (sizeof (float) * numSamples * numChannelsRunning, false);

        for (int i = 0; i < numChannelsRunning; ++i)
            converter->convertSamples (scratch.getData(), i, data[i], 0, numSamples);

        numDone = snd_pcm_writei (handle, scratch.getData(), numSamples);
    }
    else
    {
        for (int i = 0; i < numChannelsRunning; ++i)
            converter->convertSamples (data[i], data[i], numSamples);

        numDone = snd_pcm_writen (handle, (void**) data, numSamples);
    }

    if (numDone < 0 && failed (snd_pcm_recover (handle, numDone, 1 /* silent */)))
    {
        return false;
    } 
    else if (numDone < numSamples) 
    {
        IF_ALSA_VERBOSE("Did not write all samples !? numDone=" << numDone << ", numSamples=" << numSamples);
    }

    return true;
}

bool readFromInputDevice (AudioSampleBuffer& inputChannelBuffer, const int numSamples)
{
    jassert (numChannelsRunning <= inputChannelBuffer.getNumChannels());
    float** const data = inputChannelBuffer.getArrayOfChannels();

    if (isInterleaved)
    {
        scratch.ensureSize (sizeof (float) * numSamples * numChannelsRunning, false);
        scratch.fillWith (0); // (not clearing this data causes warnings in valgrind)

        snd_pcm_sframes_t num = snd_pcm_readi (handle, scratch.getData(), numSamples);

        if (num < 0 && failed (snd_pcm_recover (handle, num, 1 /* silent */)))
        {
            return false;
        }
        else if (num < numSamples)
        {
            IF_ALSA_VERBOSE("Did not read all samples !? num=" << num << ", numSamples=" << numSamples);
        }

        for (int i = 0; i < numChannelsRunning; ++i)
            converter->convertSamples (data[i], 0, scratch.getData(), i, numSamples);
    }
    else
    {
        snd_pcm_sframes_t num = snd_pcm_readn (handle, (void**) data, numSamples);

        if (num < 0 && failed (snd_pcm_recover (handle, num, 1 /* silent */)))
            return false;
        else if (num < numSamples)
        {
            IF_ALSA_VERBOSE("Did not read all samples !? num=" << num << ", numSamples=" << numSamples);
        }

        for (int i = 0; i < numChannelsRunning; ++i)
            converter->convertSamples (data[i], data[i], numSamples);
    }

    return true;
}

//==============================================================================
snd_pcm_t* handle;
String error;
int bitDepth, numChannelsRunning, latency;

//==============================================================================

private:
const bool isInput;
bool isInterleaved;
MemoryBlock scratch;
ScopedPointerAudioData::Converter converter;

//==============================================================================
template <class SampleType>
struct ConverterHelper
{
    static AudioData::Converter* createConverter (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels)
    {
        if (forInput)
        {
            typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DestType;

            if (isLittleEndian)
                return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const>, DestType> (numInterleavedChannels, 1);
            else
                return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::BigEndian, AudioData::Interleaved, AudioData::Const>, DestType> (numInterleavedChannels, 1);
        }
        else
        {
            typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SourceType;

            if (isLittleEndian)
                return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> > (1, numInterleavedChannels);
            else
                return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::BigEndian, AudioData::Interleaved, AudioData::NonConst> > (1, numInterleavedChannels);
        }
    }
};

static AudioData::Converter* createConverter (const bool forInput, const int bitDepth, const bool isFloat, const bool isLittleEndian, const int numInterleavedChannels)
{
    switch (bitDepth)
    {
        case 16:    return ConverterHelper <AudioData::Int16>::createConverter (forInput, isLittleEndian,  numInterleavedChannels);
        case 24:    return ConverterHelper <AudioData::Int24>::createConverter (forInput, isLittleEndian,  numInterleavedChannels);
        case 32:    return isFloat ? ConverterHelper <AudioData::Float32>::createConverter (forInput, isLittleEndian,  numInterleavedChannels)
                                   : ConverterHelper <AudioData::Int32>::createConverter (forInput, isLittleEndian,  numInterleavedChannels);
        default:    jassertfalse; break; // unsupported format!
    }

    return nullptr;
}

//==============================================================================

bool failed_ (const int errorNum, const char *what, int lnum)
{
if (errorNum >= 0)
return false;
error = snd_strerror (errorNum);
DBG ("ALSA error: " + error + “\n”);
IF_ALSA_VERBOSE("ALSA error " << errorNum << ", " << what << “:” << lnum);
return true;
}

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSADevice);

};

//==============================================================================
class ALSAThread : public Thread
{
public:
ALSAThread (const String& inputId_,
const String& outputId_)
: Thread (“Juce ALSA”),
sampleRate (0),
bufferSize (0),
outputLatency (0),
inputLatency (0),
callback (0),
inputId (inputId_),
outputId (outputId_),
numCallbacks (0),
audioIoInProgress(false),
inputChannelBuffer (1, 1),
outputChannelBuffer (1, 1)
{
initialiseRatesAndChannels();
}

~ALSAThread()
{
    close();
}

void open (BigInteger inputChannels,
           BigInteger outputChannels,
           const double sampleRate_,
           const int bufferSize_)
{
    close();

    error = String::empty;
    sampleRate = sampleRate_;
    bufferSize = bufferSize_;

    inputChannelBuffer.setSize (jmax ((int) minChansIn, inputChannels.getHighestBit()) + 1, bufferSize);
    inputChannelBuffer.clear();
    inputChannelDataForCallback.clear();
    currentInputChans.clear();

    if (inputChannels.getHighestBit() >= 0)
    {
        for (int i = 0; i <= jmax (inputChannels.getHighestBit(), (int) minChansIn); ++i)
        {
            if (inputChannels[i])
            {
                inputChannelDataForCallback.add (inputChannelBuffer.getSampleData (i));
                currentInputChans.setBit (i);
            }
        }
    }

    outputChannelBuffer.setSize (jmax ((int) minChansOut, outputChannels.getHighestBit()) + 1, bufferSize);
    outputChannelBuffer.clear();
    outputChannelDataForCallback.clear();
    currentOutputChans.clear();

    if (outputChannels.getHighestBit() >= 0)
    {
        for (int i = 0; i <= jmax (outputChannels.getHighestBit(), (int) minChansOut); ++i)
        {
            if (outputChannels[i])
            {
                outputChannelDataForCallback.add (outputChannelBuffer.getSampleData (i));
                currentOutputChans.setBit (i);
            }
        }
    }

    if (outputChannelDataForCallback.size() > 0 && outputId.isNotEmpty())
    {
        outputDevice = new ALSADevice (outputId, false);

        if (outputDevice->error.isNotEmpty())
        {
            error = outputDevice->error;
            outputDevice = nullptr;
            return;
        }

        currentOutputChans.setRange (0, minChansOut, true);

        if (! outputDevice->setParameters ((unsigned int) sampleRate,
                                           jlimit ((int) minChansOut, (int) maxChansOut, currentOutputChans.getHighestBit() + 1),
                                           bufferSize))
        {
            error = outputDevice->error;
            outputDevice = nullptr;
            return;
        }

        outputLatency = outputDevice->latency;
    }

    if (inputChannelDataForCallback.size() > 0 && inputId.isNotEmpty())
    {            
        inputDevice = new ALSADevice (inputId, true);

        if (inputDevice->error.isNotEmpty())
        {
            error = inputDevice->error;
            inputDevice = nullptr;
            return;
        }

        currentInputChans.setRange (0, minChansIn, true);

        if (! inputDevice->setParameters ((unsigned int) sampleRate,
                                          jlimit ((int) minChansIn, (int) maxChansIn, currentInputChans.getHighestBit() + 1),
                                          bufferSize))
        {
            error = inputDevice->error;
            inputDevice = nullptr;
            return;
        }

        inputLatency = inputDevice->latency;
    }

    if (outputDevice == nullptr && inputDevice == nullptr)
    {
        error = "no channels";
        return;
    }

    if (outputDevice != nullptr && inputDevice != nullptr)
    {
        snd_pcm_link (outputDevice->handle, inputDevice->handle);
    }

    if (inputDevice != nullptr && failed (snd_pcm_prepare (inputDevice->handle)))
        return;

    if (outputDevice != nullptr && failed (snd_pcm_prepare (outputDevice->handle)))
        return;

    startThread (9);

    int count = 1000;

    while (numCallbacks == 0)
    {
        sleep (5);

        if (--count < 0 || ! isThreadRunning())
        {
            error = "device didn't start";
            break;
        }
    }
}

void close()
{
    if (isThreadRunning()) {
        /* problem: when pulseaudio is suspended (with pasuspend) , the ALSAThread::run is just stuck in
           snd_pcm_writei -- no error, no nothing it just stays stuck. So the only way I found to exit "nicely"
           (that is without the "killing thread by force" of stopThread) , is to just call snd_pcm_close from
           here which will cause the thread to resume, and exit */
        signalThreadShouldExit();
        int numCallbacks0 = numCallbacks;
        bool exited = waitForThreadToExit(400);
        if (!exited && audioIoInProgress && numCallbacks == numCallbacks0) {
            IF_ALSA_VERBOSE("ALSA thread is stuck in a I/O.. Is pulseaudio suspended ? Now trying to wake it up a bit rudely");
            if (outputDevice) outputDevice->closeNow();
            if (inputDevice) inputDevice->closeNow();
        }
    }
    stopThread (6000);

    inputDevice = nullptr;
    outputDevice = nullptr;

    inputChannelBuffer.setSize (1, 1);
    outputChannelBuffer.setSize (1, 1);

    numCallbacks = 0;
}

void setCallback (AudioIODeviceCallback* const newCallback) noexcept
{
    const ScopedLock sl (callbackLock);
    callback = newCallback;
}

void run()
{
    while (! threadShouldExit())
    {
        if (inputDevice != nullptr && inputDevice->handle)
        {
            audioIoInProgress = true;
            if (! inputDevice->readFromInputDevice (inputChannelBuffer, bufferSize))
            {
                DBG ("ALSA: read failure");
                break;
            }
            audioIoInProgress = false;
        }

        if (threadShouldExit())
            break;

        {
            const ScopedLock sl (callbackLock);
            ++numCallbacks;

            if (callback != nullptr)
            {
                callback->audioDeviceIOCallback ((const float**) inputChannelDataForCallback.getRawDataPointer(),
                                                 inputChannelDataForCallback.size(),
                                                 outputChannelDataForCallback.getRawDataPointer(),
                                                 outputChannelDataForCallback.size(),
                                                 bufferSize);
            }
            else
            {
                for (int i = 0; i < outputChannelDataForCallback.size(); ++i)
                    zeromem (outputChannelDataForCallback[i], sizeof (float) * bufferSize);
            }
        }

        if (outputDevice != nullptr && outputDevice->handle)
        {
            failed (snd_pcm_wait (outputDevice->handle, 2000));

            if (threadShouldExit())
                break;

            snd_pcm_sframes_t avail = snd_pcm_avail_update (outputDevice->handle);
            if (avail < 0) { failed (snd_pcm_recover(outputDevice->handle, avail, 0)); } 

            audioIoInProgress = true;
            if (! outputDevice->writeToOutputDevice (outputChannelBuffer, bufferSize))
            {
                DBG ("ALSA: write failure");
                break;
            }
            audioIoInProgress = false;
        }
    }
    audioIoInProgress = false;
}

int getBitDepth() const noexcept
{
    if (outputDevice != nullptr)
        return outputDevice->bitDepth;

    if (inputDevice != nullptr)
        return inputDevice->bitDepth;

    return 16;
}

//==============================================================================
String error;
double sampleRate;
int bufferSize, outputLatency, inputLatency;
BigInteger currentInputChans, currentOutputChans;

Array <int> sampleRates;
StringArray channelNamesOut, channelNamesIn;
AudioIODeviceCallback* callback;

private:
//==============================================================================
const String inputId, outputId;
ScopedPointer outputDevice, inputDevice;
int numCallbacks;
bool audioIoInProgress;

CriticalSection callbackLock;

AudioSampleBuffer inputChannelBuffer, outputChannelBuffer;
Array<float*> inputChannelDataForCallback, outputChannelDataForCallback;

unsigned int minChansOut, maxChansOut;
unsigned int minChansIn, maxChansIn;

bool failed_ (const int errorNum, const char *what, int lnum)
{
    if (errorNum >= 0)
        return false;
    error = snd_strerror (errorNum);
    DBG ("ALSA error: " + error + "\n");
    IF_ALSA_VERBOSE("ALSA error " << errorNum << ", " << what << ":" << lnum);
    return true;
}

void initialiseRatesAndChannels()
{
    sampleRates.clear();
    channelNamesOut.clear();
    channelNamesIn.clear();
    minChansOut = 0;
    maxChansOut = 0;
    minChansIn = 0;
    maxChansIn = 0;
    unsigned int dummy = 0;

    getDeviceProperties (inputId, dummy, dummy, minChansIn, maxChansIn, sampleRates, false, true);
    getDeviceProperties (outputId, minChansOut, maxChansOut, dummy, dummy, sampleRates, true, false);

    unsigned int i;
    for (i = 0; i < maxChansOut; ++i)
        channelNamesOut.add ("channel " + String ((int) i + 1));

    for (i = 0; i < maxChansIn; ++i)
        channelNamesIn.add ("channel " + String ((int) i + 1));
}

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSAThread);

};

//==============================================================================
class ALSAAudioIODevice : public AudioIODevice
{
public:
ALSAAudioIODevice (const String& deviceName,
const String& typeName,
const String& inputId_,
const String& outputId_)
: AudioIODevice (deviceName, typeName),
inputId (inputId_),
outputId (outputId_),
isOpen_ (false),
isStarted (false),
internal (inputId_, outputId_)
{
}

~ALSAAudioIODevice()
{
    close();
}

StringArray getOutputChannelNames()             { return internal.channelNamesOut; }
StringArray getInputChannelNames()              { return internal.channelNamesIn; }

int getNumSampleRates()                         { return internal.sampleRates.size(); }
double getSampleRate (int index)                { return internal.sampleRates [index]; }

int getDefaultBufferSize()                      { return 512; }
int getNumBufferSizesAvailable()                { return 50; }

int getBufferSizeSamples (int index)
{
    int n = 16;
    for (int i = 0; i < index; ++i)
        n += n < 64 ? 16
                    : (n < 512 ? 32
                               : (n < 1024 ? 64
                                           : (n < 2048 ? 128 : 256)));

    return n;
}

String open (const BigInteger& inputChannels,
             const BigInteger& outputChannels,
             double sampleRate,
             int bufferSizeSamples)
{
    close();

    if (bufferSizeSamples <= 0)
        bufferSizeSamples = getDefaultBufferSize();

    if (sampleRate <= 0)
    {
        for (int i = 0; i < getNumSampleRates(); ++i)
        {
            if (getSampleRate (i) >= 44100)
            {
                sampleRate = getSampleRate (i);
                break;
            }
        }
    }

    internal.open (inputChannels, outputChannels,
                   sampleRate, bufferSizeSamples);

    isOpen_ = internal.error.isEmpty();
    return internal.error;
}

void close()
{
    stop();
    internal.close();
    isOpen_ = false;
}

bool isOpen()                           { return isOpen_; }
bool isPlaying()                        { return isStarted && internal.error.isEmpty(); }
String getLastError()                   { return internal.error; }

int getCurrentBufferSizeSamples()       { return internal.bufferSize; }
double getCurrentSampleRate()           { return internal.sampleRate; }
int getCurrentBitDepth()                { return internal.getBitDepth(); }

BigInteger getActiveOutputChannels() const    { return internal.currentOutputChans; }
BigInteger getActiveInputChannels() const     { return internal.currentInputChans; }

int getOutputLatencyInSamples()         { return internal.outputLatency; }
int getInputLatencyInSamples()          { return internal.inputLatency; }

void start (AudioIODeviceCallback* callback)
{
    if (! isOpen_)
        callback = nullptr;

    if (callback != nullptr)
        callback->audioDeviceAboutToStart (this);

    internal.setCallback (callback);

    isStarted = (callback != nullptr);
}

void stop()
{
    AudioIODeviceCallback* const oldCallback = internal.callback;

    start (0);

    if (oldCallback != nullptr)
        oldCallback->audioDeviceStopped();
}

String inputId, outputId;

private:
bool isOpen_, isStarted;
ALSAThread internal;
};

//==============================================================================
class ALSAAudioIODeviceType : public AudioIODeviceType
{
static void alsaSilentErrorHandler(const char */file/, int /line/, const char */function/, int /err/, const char */fmt/,…) {
}

public:
//==============================================================================
ALSAAudioIODeviceType(bool listOnlySoundcards_, const String &typeName)
: AudioIODeviceType (typeName),
hasScanned (false), listOnlySoundcards(listOnlySoundcards_)
{
const char *v = getenv(“ALSA_VERBOSE”);
if (v && v[0] == ‘1’) alsa_verbose = 1;

    if (!alsa_verbose) 
    {
        snd_lib_error_set_handler (&ALSAAudioIODeviceType::alsaSilentErrorHandler);
    }
}

~ALSAAudioIODeviceType()
{
    if (alsa_verbose) 
    {
        snd_lib_error_set_handler(0);
    }
    snd_config_update_free_global(); // prevent valgrind from screaming about alsa leaks
}

//==============================================================================
void scanForDevices()
{
    if (hasScanned)
        return;

    hasScanned = true;
    inputNames.clear();
    inputIds.clear();
    outputNames.clear();
    outputIds.clear();

    IF_ALSA_VERBOSE("ALSAAudioIODeviceType::scanForDevices()");

    if (listOnlySoundcards) 
    {
        enumerateAlsaSoundcards();
    } 
    else 
    {
        enumerateAlsaPcmDevices();
    }

    inputNames.appendNumbersToDuplicates (false, true);
    outputNames.appendNumbersToDuplicates (false, true);
}

StringArray getDeviceNames (bool wantInputNames) const
{
    jassert (hasScanned); // need to call scanForDevices() before doing this

    return wantInputNames ? inputNames : outputNames;
}

int getDefaultDeviceIndex (bool forInput) const
{
    int idx = 0;
    if (forInput) idx = inputIds.indexOf("default"); else idx = outputIds.indexOf("default");
    jassert (hasScanned); // need to call scanForDevices() before doing this
    return idx >= 0 ? idx : 0;
}

bool hasSeparateInputsAndOutputs() const    { return true; }

int getIndexOfDevice (AudioIODevice* device, bool asInput) const
{
    jassert (hasScanned); // need to call scanForDevices() before doing this

    ALSAAudioIODevice* d = dynamic_cast <ALSAAudioIODevice*> (device);
    if (d == nullptr)
        return -1;

    return asInput ? inputIds.indexOf (d->inputId)
                   : outputIds.indexOf (d->outputId);
}

AudioIODevice* createDevice (const String& outputDeviceName,
                             const String& inputDeviceName)
{
    jassert (hasScanned); // need to call scanForDevices() before doing this

    const int inputIndex = inputNames.indexOf (inputDeviceName);
    const int outputIndex = outputNames.indexOf (outputDeviceName);

    String deviceName (outputIndex >= 0 ? outputDeviceName
                                        : inputDeviceName);

    if (inputIndex >= 0 || outputIndex >= 0)
        return new ALSAAudioIODevice (deviceName, getTypeName(), 
                                      inputIds [inputIndex],
                                      outputIds [outputIndex]);

    return nullptr;
}

//==============================================================================

private:

StringArray inputNames, outputNames, inputIds, outputIds;
bool hasScanned, listOnlySoundcards;

bool testDevice(const String &id, const String &outputName, const String &inputName) {
    unsigned int minChansOut = 0, maxChansOut = 0;
    unsigned int minChansIn = 0, maxChansIn = 0;
    Array <int> rates;

    bool isInput = inputName.isNotEmpty(), isOutput = outputName.isNotEmpty();
    getDeviceProperties (id, minChansOut, maxChansOut, minChansIn, maxChansIn, rates, isOutput, isInput);

    isInput = maxChansIn > 0;
    isOutput = maxChansOut > 0;

    if ((isInput || isOutput) && rates.size() > 0) 
    {
        IF_ALSA_VERBOSE("testDevice: '" << id.toUTF8().getAddress() << "' -> isInput: " << isInput << ", isOutput: " << isOutput);
      
        if (isInput)
        {
            inputNames.add(inputName);
            inputIds.add(id);
        }
    
        if (isOutput)
        {
            outputNames.add(outputName);
            outputIds.add(id);
        }
        return (isInput || isOutput);
    } else return false;
}

void enumerateAlsaSoundcards() {
    snd_ctl_t* handle = nullptr;
    snd_ctl_card_info_t* info = nullptr;
    snd_ctl_card_info_alloca (&info);
    
    int cardNum = -1;
    while (outputIds.size() + inputIds.size() <= 64) 
    {
        snd_card_next (&cardNum);
        
        if (cardNum < 0)
            break;
        
        if (CHECKED_ALSA(snd_ctl_open (&handle, ("hw:" + String (cardNum)).toUTF8(), SND_CTL_NONBLOCK)) >= 0) 
        {
            if (CHECKED_ALSA(snd_ctl_card_info (handle, info)) >= 0) 
            {
                String cardId (snd_ctl_card_info_get_id (info));
                
                if (cardId.removeCharacters ("0123456789").isEmpty())
                    cardId = String (cardNum);
                
                String cardName = snd_ctl_card_info_get_name (info);
                if (cardName.isEmpty()) cardName = cardId;
                
                int device = -1;
                
                snd_pcm_info_t *pcmInfo; snd_pcm_info_alloca( &pcmInfo );
                
                for (;;) 
                {
                    if (snd_ctl_pcm_next_device (handle, &device) < 0 || device < 0)
                        break;
                    
                    
                    snd_pcm_info_set_device (pcmInfo, device);
                    for (int subDevice=0, nbSubDevice=1; subDevice < nbSubDevice; ++subDevice) 
                    {
                        snd_pcm_info_set_subdevice (pcmInfo, subDevice);
                        snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_CAPTURE);
                        bool isInput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
                        snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_PLAYBACK);
                        bool isOutput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
                        
                        if (!isInput && !isOutput) continue;
                        
                        if (nbSubDevice == 1) 
                        {
                            nbSubDevice = snd_pcm_info_get_subdevices_count (pcmInfo);
                        }
                        
                        String id, name;
                        
                        if (nbSubDevice == 1) 
                        {
                            id << "hw:" << cardId << "," << device;
                            name << cardName << ", " << snd_pcm_info_get_name (pcmInfo);
                        } else {
                            id << "hw:" << cardId << "," << device << "," << subDevice;
                            name << cardName << ", " << snd_pcm_info_get_name (pcmInfo) << " {" <<  snd_pcm_info_get_subdevice_name (pcmInfo) << "}";
                        }

                        IF_ALSA_VERBOSE("Soundcard ID: " << id << ", name: '" << name << ", isInput:" << isInput << ", isOutput:" << isOutput << "\n");
                        if (isInput) 
                        {
                            inputNames.add(name);
                            inputIds.add(id);
                        }
                        if (isOutput) 
                        {
                            outputNames.add(name);
                            outputIds.add(id);
                        }
                    }
                }
            }
            
            CHECKED_ALSA(snd_ctl_close (handle));
        }
    }
}

/* enumerate all ALSA output devices (as output by the command aplay -L)
   the enumerate does not try to open the devices (with "testDevice" for example), 
   that way we also list the devices that are busy and not yet available */
void enumerateAlsaPcmDevices() {
    void **hints = 0;
    if (CHECKED_ALSA(snd_device_name_hint(-1, "pcm", &hints)) == 0) 
    {
        for (char **h = (char**)hints; *h; ++h) 
        {
            String id, description, ioid;
            {
                char *aid = 0, *adesc = 0, *aioid = 0;
                aid = snd_device_name_get_hint(*h, "NAME"); 
                adesc = snd_device_name_get_hint(*h, "DESC");
                aioid = snd_device_name_get_hint(*h, "IOID"); // NULL, or Input or Output; NULL means Input+Output
                id << aid; 
                description << adesc;
                ioid << aioid;
                free(aid); free(adesc); free(aioid);
            }
            IF_ALSA_VERBOSE("ID: " << id << "; desc: " << description << "; ioid: " << ioid);
            String ss = id.fromFirstOccurrenceOf("=", false, false).upToFirstOccurrenceOf(",", false, false);
            
            /* skip uninteresting entries */
            if (id.isEmpty() || 
                id.startsWith("default:") || 
                id.startsWith("sysdefault:") || 
                id.startsWith("plughw:") || 
                id == "null") continue;
            
            String name = String(description).replace("\n", "; ");
            if (name.isEmpty()) name = id;

            bool isOutput = (ioid != "Input");
            bool isInput = (ioid != "Output");
            /* alsa is stupid here, it advertises dmix and dsnoop as input/output devices, and while opening
               dmix as input, or dsnoop as output will cause it to burp errors in the terminal.. */
            isInput  = isInput  && !id.startsWith("dmix");
            isOutput = isOutput && !id.startsWith("dsnoop");
            if (isInput) 
            {
                inputNames.add(name);
                inputIds.add(id);
            }
            if (isOutput) 
            {
                outputNames.add(name);
                outputIds.add(id);
            }                
        }

        snd_device_name_free_hint(hints);
    }



    /* sometimes the "default" device is not listed, but it is nice to see it explicitely in the list */
    if (!outputIds.contains("default")) 
    {
        testDevice("default", "Default ALSA Output", "Default ALSA Input");
    }
    /* same for the pulseaudio plugin */
    if (!outputIds.contains("pulse")) 
    {
        testDevice("pulse", "Pulseaudio output", "Pulseaudio input");
    }
    
    /* make sure the default device is listed first, and followed by the pulse device (if present) */
    int idx;
    idx = outputIds.indexOf("pulse");
    moveToFront(outputIds, idx); moveToFront(outputNames, idx);
    idx = inputIds.indexOf("pulse");
    moveToFront(inputIds, idx); moveToFront(inputNames, idx);
    idx = outputIds.indexOf("default");
    moveToFront(outputIds, idx); moveToFront(outputNames, idx);
    idx = inputIds.indexOf("default");
    moveToFront(inputIds, idx); moveToFront(inputNames, idx);
}

static void moveToFront(StringArray &a, int idx) 
{
    if (idx > 0) 
    {
        String s = a[idx]; a.remove(idx); a.insert(0, s);
    }
}

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSAAudioIODeviceType);

};

}

//==============================================================================

AudioIODeviceType* createAudioIODeviceType_ALSA_Soundcards()
{
return new ALSAAudioIODeviceType(true, “ALSA HW”);
}

AudioIODeviceType* createAudioIODeviceType_ALSA_PcmDevices()
{
return new ALSAAudioIODeviceType(false, “ALSA”);
}

AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA()
{
return createAudioIODeviceType_ALSA_PcmDevices();
}

[/code]

juce_linux_Midi.cpp:

/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-11 by Raw Material Software Ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

  ==============================================================================
*/

#if JUCE_ALSA

using std::cerr;

// You can define these strings in your app if you want to override the default names:
#ifndef JUCE_ALSA_MIDI_INPUT_NAME
 #define JUCE_ALSA_MIDI_INPUT_NAME  "Juce Midi Input"
#endif

#ifndef JUCE_ALSA_MIDI_OUTPUT_NAME
 #define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output"
#endif

//==============================================================================
namespace
{

class AlsaPortAndCallback;

class AlsaClient : public ReferenceCountedObject 
{
    class MidiInputThread   : public Thread
    {
    public:
        MidiInputThread (AlsaClient *client_) 
        : Thread ("Juce MIDI Input"),
          client(client_)
        {
            jassert (client->input && client->get());
        }

        ~MidiInputThread()
        {
        }

        void run()
        {
            const int maxEventSize = 16 * 1024;
            snd_midi_event_t* midiParser;
            snd_seq_t *seqHandle = client->get();
            if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
            {
                HeapBlock <uint8> buffer (maxEventSize);

                const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
                struct pollfd* const pfd = (struct pollfd*) alloca (numPfds * sizeof (struct pollfd));

                snd_seq_poll_descriptors (seqHandle, pfd, numPfds, POLLIN);

                while (! threadShouldExit())
                {
                    if (poll (pfd, numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call
                    {
                        if (threadShouldExit()) break;  // avoids a "killing thread by force" message
                        snd_seq_event_t* inputEvent = nullptr;

                        snd_seq_nonblock (seqHandle, 1);

                        do
                        {
                            if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
                            {
                                // xxx what about SYSEXes that are too big for the buffer?
                                const int numBytes = snd_midi_event_decode (midiParser, buffer, maxEventSize, inputEvent);

                                snd_midi_event_reset_decode (midiParser);

                                if (numBytes > 0)
                                {
                                    const MidiMessage message ((const uint8*) buffer,
                                                               numBytes,
                                                               Time::getMillisecondCounter() * 0.001);
                                    client->handleIncomingMidiMessage(message, inputEvent->dest.port);
                                }

                                snd_seq_free_event (inputEvent);
                            }
                        }
                        while (snd_seq_event_input_pending (seqHandle, 0) > 0);

                        snd_seq_free_event (inputEvent);
                    }
                }

                snd_midi_event_free (midiParser);
            }
        };

    private:
        AlsaClient *client;
    };

    bool input;
    snd_seq_t *handle;

    Array<AlsaPortAndCallback*> activeCallbacks;
    CriticalSection callbackLock;
    ScopedPointer<MidiInputThread> inputThread;


public:
    typedef ReferenceCountedObjectPtr<AlsaClient> Ptr;

    AlsaClient(bool in) : input(in) 
    {
        if (snd_seq_open(&handle, "default", in ? SND_SEQ_OPEN_INPUT : SND_SEQ_OPEN_OUTPUT, 0) != 0) handle = 0;
    }

    ~AlsaClient() 
    {
        if (handle) 
        {
            snd_seq_close(handle); 
            handle=0; 
        }
        jassert(activeCallbacks.size() == 0);
        if (inputThread) 
        { 
            inputThread->stopThread(3000); 
            inputThread = 0; 
        }
    }

    bool isInput() { return input; }

    void setName(const String &name) 
    { 
        snd_seq_set_client_name (handle, name.toUTF8());
    }

    void registerCallback(AlsaPortAndCallback *cb) {
        if (cb) 
        {
            { 
                ScopedLock sl(callbackLock);
                activeCallbacks.add(cb); 
                if (inputThread == 0) 
                {
                    inputThread = new MidiInputThread(this);
                }
            }
            inputThread->startThread();
        }
    }

    void unregisterCallback(AlsaPortAndCallback *cb) {
        ScopedLock sl(callbackLock);
        jassert(activeCallbacks.contains(cb)); 
        activeCallbacks.removeAllInstancesOf(cb);
        if (activeCallbacks.size() == 0 && inputThread->isThreadRunning()) 
        {
            inputThread->signalThreadShouldExit();
        }
    }

    void handleIncomingMidiMessage(const MidiMessage &message, int port);
    snd_seq_t *get() const { return handle; }
};


// this singleton will be created on the first call of this function -- the compiler takes care of thread safety
static AlsaClient::Ptr globalAlsaSequencerIn() 
{
    static AlsaClient::Ptr global = new AlsaClient(true);
    return global;
}

static AlsaClient::Ptr globalAlsaSequencerOut() 
{
    static AlsaClient::Ptr global = new AlsaClient(false);
    return global;
}

static AlsaClient::Ptr globalAlsaSequencer(bool input) 
{
    if (input) return globalAlsaSequencerIn(); else return globalAlsaSequencerOut();
}

/* handle an input or output port of the supplied AlsaClient */
class AlsaPort 
{
public:
    AlsaPort(AlsaClient::Ptr h, int p) : handle(h), portId(p) {}
    AlsaPort() : portId(-1) {}
    void createPort(AlsaClient::Ptr h, const String &name, bool forInput) {
        handle = h;
        if (handle->get()) 
        {
            portId = snd_seq_create_simple_port (handle->get(), name.toUTF8(), 
                                                 forInput ? (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)
                                                 : (SND_SEQ_PORT_CAP_READ  | SND_SEQ_PORT_CAP_SUBS_READ),
                                                 SND_SEQ_PORT_TYPE_MIDI_GENERIC);
        }
    }
    void deletePort() 
    { 
        if (isValid()) 
        {
            snd_seq_delete_simple_port(handle->get(), portId); 
            portId = -1;
        }
    }

    void connectWith(int sourceClient, int sourcePort) {
        if (handle->isInput()) 
            snd_seq_connect_from (handle->get(), portId, sourceClient, sourcePort);
        else snd_seq_connect_to (handle->get(), portId, sourceClient, sourcePort);
    }

    bool isValid() const { return handle && handle->get() && portId >= 0; }

    AlsaClient::Ptr handle; // reference to the alsa client
    int portId;             // alsa port id
};

class AlsaPortAndCallback 
{
public:
    AlsaPort port;
    MidiInput *midiInput;
    MidiInputCallback *callback;
    bool callbackEnabled;

    AlsaPortAndCallback(AlsaPort p, MidiInput *in, MidiInputCallback *cb) : 
        port(p), midiInput(in), callback(cb), callbackEnabled(false) 
    {
    }

    ~AlsaPortAndCallback() {
        if (port.handle->get()) 
        {
            port.deletePort();
            toggleCallback(false);
        }
    }
    void toggleCallback(bool enable) { 
        if (callbackEnabled != enable) 
        {
            callbackEnabled = enable; 
            if (enable) port.handle->registerCallback(this); 
            else port.handle->unregisterCallback(this);
        }
    }
};

void AlsaClient::handleIncomingMidiMessage(const MidiMessage &message, int port) {
    ScopedLock sl(callbackLock);
    AlsaPortAndCallback *cb = activeCallbacks[port];
    if (cb) cb->callback->handleIncomingMidiMessage(cb->midiInput, message);
}

AlsaPort iterateMidiClient (AlsaClient::Ptr seq,
                            snd_seq_client_info_t* clientInfo,
                            const bool forInput,
                            StringArray& deviceNamesFound,
                            const int deviceIndexToOpen)
{
    AlsaPort port;

    snd_seq_t *seqHandle = seq->get();
    snd_seq_port_info_t* portInfo;
    if (snd_seq_port_info_malloc (&portInfo) == 0)
    {
        int numPorts = snd_seq_client_info_get_num_ports (clientInfo);
        const int client = snd_seq_client_info_get_client (clientInfo);

        snd_seq_port_info_set_client (portInfo, client);
        snd_seq_port_info_set_port (portInfo, -1);

        while (--numPorts >= 0)
        {
            if (snd_seq_query_next_port (seqHandle, portInfo) == 0
                && (snd_seq_port_info_get_capability (portInfo)
                    & (forInput ? SND_SEQ_PORT_CAP_READ
                       : SND_SEQ_PORT_CAP_WRITE)) != 0)
            {
                deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo));

                if (deviceNamesFound.size() == deviceIndexToOpen + 1)
                {
                    const int sourcePort = snd_seq_port_info_get_port (portInfo);
                    const int sourceClient = snd_seq_client_info_get_client (clientInfo);
                    if (sourcePort != -1)
                    {
                        const String name = forInput ? JUCE_ALSA_MIDI_INPUT_NAME : JUCE_ALSA_MIDI_OUTPUT_NAME;
                        seq->setName(name);
                        port.createPort(seq, name, forInput);
                        port.connectWith(sourceClient, sourcePort);
                    }
                }
            }
        }

        snd_seq_port_info_free (portInfo);
    }

    return port;
}

AlsaPort iterateMidiDevices (const bool forInput,
                             StringArray& deviceNamesFound,
                             const int deviceIndexToOpen)
{
    AlsaPort port;
    AlsaClient::Ptr client = globalAlsaSequencer(forInput);
    snd_seq_t *seqHandle = client->get();

    if (seqHandle)
    {
        snd_seq_system_info_t* systemInfo = nullptr;
        snd_seq_client_info_t* clientInfo = nullptr;

        if (snd_seq_system_info_malloc (&systemInfo) == 0)
        {
            if (snd_seq_system_info (seqHandle, systemInfo) == 0
                && snd_seq_client_info_malloc (&clientInfo) == 0)
            {
                int numClients = snd_seq_system_info_get_cur_clients (systemInfo);

                while (--numClients >= 0 && !port.isValid())
                    if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
                        port = iterateMidiClient (client, clientInfo,
                                                   forInput, deviceNamesFound,
                                                   deviceIndexToOpen);

                snd_seq_client_info_free (clientInfo);
            }

            snd_seq_system_info_free (systemInfo);
        }

    }

    deviceNamesFound.appendNumbersToDuplicates (true, true);

    return port;
}

AlsaPort createMidiDevice (const bool forInput, const String& deviceNameToOpen)
{
    AlsaClient::Ptr client(new AlsaClient(forInput));
    AlsaPort port;
    if (client->get()) 
    {
        client->setName(deviceNameToOpen + (forInput ? " Input" : " Output"));

        port.createPort(client, forInput ? "in" : "out", forInput);
    } // otherwise the AlsaClient will be automatically freed

    return port;
}

//==============================================================================
class MidiOutputDevice
{
public:
    MidiOutputDevice (MidiOutput* const midiOutput_,
                      AlsaPort port_)
        :
          midiOutput (midiOutput_),
          port(port_),
          maxEventSize (16 * 1024)
    {
        jassert (port.isValid() && midiOutput != 0);
        snd_midi_event_new (maxEventSize, &midiParser);
    }

    ~MidiOutputDevice()
    {
        snd_midi_event_free (midiParser);
    }

    void sendMessageNow (const MidiMessage& message)
    {
        if (message.getRawDataSize() > maxEventSize)
        {
            maxEventSize = message.getRawDataSize();
            snd_midi_event_free (midiParser);
            snd_midi_event_new (maxEventSize, &midiParser);
        }

        snd_seq_event_t event;
        snd_seq_ev_clear (&event);

        long numBytes = (long) message.getRawDataSize();
        const uint8* data = message.getRawData();
        
        snd_seq_t *seqHandle = port.handle->get();

        while (numBytes > 0)
        {
            const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
            if (numSent <= 0)
                break;

            numBytes -= numSent;
            data += numSent;

            snd_seq_ev_set_source (&event, 0);
            snd_seq_ev_set_subs (&event);
            snd_seq_ev_set_direct (&event);

            snd_seq_event_output (seqHandle, &event);
        }

        snd_seq_drain_output (seqHandle);
        snd_midi_event_reset_encode (midiParser);
    }

private:
    MidiOutput* const midiOutput;
    AlsaPort port;
    snd_midi_event_t* midiParser;
    int maxEventSize;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice);
};

} // namespace

StringArray MidiOutput::getDevices()
{
    StringArray devices;
    iterateMidiDevices (false, devices, -1);
    return devices;
}

int MidiOutput::getDefaultDeviceIndex()
{
    return 0;
}

MidiOutput* MidiOutput::openDevice (int deviceIndex)
{
    MidiOutput* newDevice = nullptr;

    StringArray devices;
    AlsaPort port = iterateMidiDevices (false, devices, deviceIndex);

    if (port.isValid())
    {
        newDevice = new MidiOutput();
        newDevice->internal = new MidiOutputDevice (newDevice, port);
    }

    return newDevice;
}

MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
{
    MidiOutput* newDevice = nullptr;

    AlsaPort port = createMidiDevice (false, deviceName);

    if (port.isValid())
    {
        newDevice = new MidiOutput();
        newDevice->internal = new MidiOutputDevice (newDevice, port);
    }

    return newDevice;
}

MidiOutput::~MidiOutput()
{
    delete static_cast <MidiOutputDevice*> (internal);
}

void MidiOutput::sendMessageNow (const MidiMessage& message)
{
    static_cast <MidiOutputDevice*> (internal)->sendMessageNow (message);
}


//==============================================================================

//==============================================================================
MidiInput::MidiInput (const String& name_)
    : name (name_),
      internal (0)
{
}

MidiInput::~MidiInput()
{
    stop();
    delete static_cast <AlsaPortAndCallback*> (internal);
}

void MidiInput::start()
{
    static_cast <AlsaPortAndCallback*> (internal)->toggleCallback(true);
}

void MidiInput::stop()
{
    static_cast <AlsaPortAndCallback*> (internal)->toggleCallback(false);
}

int MidiInput::getDefaultDeviceIndex()
{
    return 0;
}

StringArray MidiInput::getDevices()
{
    StringArray devices;
    iterateMidiDevices (true, devices, -1);
    return devices;
}

MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
{
    MidiInput* newDevice = nullptr;

    StringArray devices;
    AlsaPort port = iterateMidiDevices (true, devices, deviceIndex);

    if (port.isValid())
    {
        newDevice = new MidiInput (devices [deviceIndex]);
        newDevice->internal = new AlsaPortAndCallback(port, newDevice, callback);
    }

    return newDevice;
}

MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
{
    MidiInput* newDevice = nullptr;

    AlsaPort port = createMidiDevice (true, deviceName);

    if (port.isValid())
    {
        newDevice = new MidiInput (deviceName);
        newDevice->internal = new AlsaPortAndCallback(port, newDevice, callback);
    }

    return newDevice;
}



//==============================================================================
#else

// (These are just stub functions if ALSA is unavailable...)

StringArray MidiOutput::getDevices()                                { return StringArray(); }
int MidiOutput::getDefaultDeviceIndex()                             { return 0; }
MidiOutput* MidiOutput::openDevice (int)                            { return nullptr; }
MidiOutput* MidiOutput::createNewDevice (const String&)             { return nullptr; }
MidiOutput::~MidiOutput()   {}
void MidiOutput::sendMessageNow (const MidiMessage&)    {}

MidiInput::MidiInput (const String& name_) : name (name_), internal (0)  {}
MidiInput::~MidiInput() {}
void MidiInput::start() {}
void MidiInput::stop()  {}
int MidiInput::getDefaultDeviceIndex()      { return 0; }
StringArray MidiInput::getDevices()         { return StringArray(); }
MidiInput* MidiInput::openDevice (int, MidiInputCallback*)                  { return nullptr; }
MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*)   { return nullptr; }

#endif

Yikes! Thanks, but so many changes…! Will attempt to plough through it and figure out what’s going on!

Ok… Have attempted a sanity-check on this stuff - please let me know if it works!

any idea what’s going on with this bit of code?

if (sourcePort != -1) { const String name (forInput ? JUCE_ALSA_MIDI_INPUT_NAME : JUCE_ALSA_MIDI_OUTPUT_NAME); seq->setName (name); port.createPort (seq, name, forInput); port.connectWith (sourceClient, sourcePort); }

Just trying this stuff, and it’s just repeatedly creating new input devices with the name “Juce midi output” (yes, it’s an input called “output”), every time I scan for devices… You added this code that seems to create a new port if one isn’t found, but clearly the logic’s broken and I’m really unclear about what it was supposed to do?

It is when you call iterateMidiDevices in order to open (connect to) an input device. The outer loop loops on all input devices, and if the (source) device has an port (if sourcePort != 1) , it creates a new port in the juce client, and connects this port to the “sourcePort” of the input device. When you scan for devices, this function should always be called with deviceIndexToOpen == -1 so no new ports or connection should ever be made

Otherwise, everything seems to work fine to me. I would recommend that you disable the verbosity by default on juce_linux_ALSA. Also there is a small mistake in #if JUCE_ALSA_LOGGING snd_lib_error_set_handler (&silentErrorHandler); #endif
it should be #if !JUCE_ALSA_LOGGING

(I had made the same mistake in my destructor , in fact…)

This prevents a lot of dumb and irrelevant alsa errors messages.

Thanks, I’ll sort out that logging.

Can’t figure out what’s going on with the midi… Certainly the name is all mixed up with “input”/“output” the wrong way round though - any idea where in this code the logic for that should be reversed?

Nope, I can’t see anything wrong here, either with my synth, or with the JuceDemo

hum wait , just found a bug with MidiOutput , it does not destroy the port when it is closed. I don’t think it is related to your issue, but that’s a bug anyway ! ~MidiOutputDevice() { snd_midi_event_free (midiParser); port.deletePort(); }

Regarding inputs and outputs, there is one global sequencer handle for all alsa input, and another one for all alsa output. I could have merged them into a single in/out handle but chose not to do it in order not to change the behaviour of juce (since the user can choose both names with JUCE_ALSA_MIDI_INPUT_NAME and JUCE_ALSA_MIDI_OUTPUT_NAME)

Thanks - I think that bug was what was causing my problem - seems to be working ok now, thanks!