text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   To use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

struct iOSAudioIODeviceType;

class iOSAudioIODevice : public AudioIODevice
{
public:
    //==============================================================================
    String open (const BigInteger&, const BigInteger&, double, int) override;
    void close() override;

    void start (AudioIODeviceCallback*) override;
    void stop() override;

    Array<double> getAvailableSampleRates() override;
    Array<int> getAvailableBufferSizes() override;

    bool setAudioPreprocessingEnabled (bool) override;

    //==============================================================================
    bool isPlaying() override;
    bool isOpen() override;
    String getLastError() override;

    //==============================================================================
    StringArray getOutputChannelNames() override;
    StringArray getInputChannelNames() override;

    int getDefaultBufferSize() override;
    int getCurrentBufferSizeSamples() override;

    double getCurrentSampleRate() override;

    int getCurrentBitDepth() override;

    BigInteger getActiveOutputChannels() const override;
    BigInteger getActiveInputChannels() const override;

    int getOutputLatencyInSamples() override;
    int getInputLatencyInSamples() override;

    int getXRunCount() const noexcept override;

    //==============================================================================
    void setMidiMessageCollector (MidiMessageCollector*);
    AudioPlayHead* getAudioPlayHead() const;

    //==============================================================================
    bool isInterAppAudioConnected() const;
   #if JUCE_MODULE_AVAILABLE_juce_graphics
    Image getIcon (int size);
   #endif
    void switchApplication();

private:
    //==============================================================================
    iOSAudioIODevice (const String&);

    //==============================================================================
    friend struct iOSAudioIODeviceType;
    friend struct AudioSessionHolder;

    struct Pimpl;
    friend struct Pimpl;
    ScopedPointer<Pimpl> pimpl;

    JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   To use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 STATICMETHOD (getMinBufferSize,            "getMinBufferSize",             "(III)I") \
 STATICMETHOD (getNativeOutputSampleRate,   "getNativeOutputSampleRate",    "(I)I") \
 METHOD (constructor,   "<init>",   "(IIIIII)V") \
 METHOD (getState,      "getState", "()I") \
 METHOD (play,          "play",     "()V") \
 METHOD (stop,          "stop",     "()V") \
 METHOD (release,       "release",  "()V") \
 METHOD (flush,         "flush",    "()V") \
 METHOD (write,         "write",    "([SII)I") \

DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack");
#undef JNI_CLASS_MEMBERS

//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
 METHOD (constructor,       "<init>",           "(IIIII)V") \
 METHOD (getState,          "getState",         "()I") \
 METHOD (startRecording,    "startRecording",   "()V") \
 METHOD (stop,              "stop",             "()V") \
 METHOD (read,              "read",             "([SII)I") \
 METHOD (release,           "release",          "()V") \

DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord");
#undef JNI_CLASS_MEMBERS

//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 STATICFIELD (SDK_INT, "SDK_INT", "I") \

 DECLARE_JNI_CLASS (AndroidBuildVersion, "android/os/Build$VERSION");
#undef JNI_CLASS_MEMBERS

//==============================================================================
enum
{
    CHANNEL_OUT_STEREO  = 12,
    CHANNEL_IN_STEREO   = 12,
    CHANNEL_IN_MONO     = 16,
    ENCODING_PCM_16BIT  = 2,
    STREAM_MUSIC        = 3,
    MODE_STREAM         = 1,
    STATE_UNINITIALIZED = 0
};

const char* const javaAudioTypeName = "Android Audio";

//==============================================================================
class AndroidAudioIODevice  : public AudioIODevice,
                              public Thread
{
public:
    //==============================================================================
    AndroidAudioIODevice (const String& deviceName)
        : AudioIODevice (deviceName, javaAudioTypeName),
          Thread ("audio"),
          minBufferSizeOut (0), minBufferSizeIn (0), callback (0), sampleRate (0),
          numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2),
          numClientOutputChannels (0), numDeviceOutputChannels (0),
          actualBufferSize (0), isRunning (false),
          inputChannelBuffer (1, 1),
          outputChannelBuffer (1, 1)
    {
        JNIEnv* env = getEnv();
        sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM);

        minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack,  AudioTrack.getMinBufferSize,  sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT);
        minBufferSizeIn  = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO,  ENCODING_PCM_16BIT);

        if (minBufferSizeIn <= 0)
        {
            minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT);

            if (minBufferSizeIn > 0)
                numDeviceInputChannelsAvailable = 1;
            else
                numDeviceInputChannelsAvailable = 0;
        }

        DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; "
              << sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable);
    }

    ~AndroidAudioIODevice()
    {
        close();
    }

    StringArray getOutputChannelNames() override
    {
        StringArray s;
        s.add ("Left");
        s.add ("Right");
        return s;
    }

    StringArray getInputChannelNames() override
    {
        StringArray s;

        if (numDeviceInputChannelsAvailable == 2)
        {
            s.add ("Left");
            s.add ("Right");
        }
        else if (numDeviceInputChannelsAvailable == 1)
        {
            s.add ("Audio Input");
        }

        return s;
    }

    Array<double> getAvailableSampleRates() override
    {
        Array<double> r;
        r.add ((double) sampleRate);
        return r;
    }

    Array<int> getAvailableBufferSizes() override
    {
        Array<int> b;
        int n = 16;

        for (int i = 0; i < 50; ++i)
        {
            b.add (n);
            n += n < 64 ? 16
                        : (n < 512 ? 32
                                   : (n < 1024 ? 64
                                               : (n < 2048 ? 128 : 256)));
        }

        return b;
    }

    int getDefaultBufferSize() override                 { return 2048; }

    String open (const BigInteger& inputChannels,
                 const BigInteger& outputChannels,
                 double requestedSampleRate,
                 int bufferSize) override
    {
        close();

        if (sampleRate != (int) requestedSampleRate)
            return "Sample rate not allowed";

        lastError.clear();
        int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;

        numDeviceInputChannels = 0;
        numDeviceOutputChannels = 0;

        activeOutputChans = outputChannels;
        activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
        numClientOutputChannels = activeOutputChans.countNumberOfSetBits();

        activeInputChans = inputChannels;
        activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
        numClientInputChannels = activeInputChans.countNumberOfSetBits();

        actualBufferSize = preferredBufferSize;
        inputChannelBuffer.setSize (2, actualBufferSize);
        inputChannelBuffer.clear();
        outputChannelBuffer.setSize (2, actualBufferSize);
        outputChannelBuffer.clear();

        JNIEnv* env = getEnv();

        if (numClientOutputChannels > 0)
        {
            numDeviceOutputChannels = 2;
            outputDevice = GlobalRef (env->NewObject (AudioTrack, AudioTrack.constructor,
                                                      STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT,
                                                      (jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast<int> (sizeof (int16))), MODE_STREAM));

            const bool supportsUnderrunCount = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24);
            getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0;

            int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState);
            if (outputDeviceState > 0)
            {
                isRunning = true;
            }
            else
            {
                 // failed to open the device
                outputDevice.clear();
                lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState);
            }
        }

        if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0)
        {
            if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
            {
                // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
                // before trying to open an audio input device. This is not going to work!
                jassertfalse;

                inputDevice.clear();
                lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO";
            }
            else
            {
                numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable);
                inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor,
                                                         0 /* (default audio source) */, sampleRate,
                                                         numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO,
                                                         ENCODING_PCM_16BIT,
                                                         (jint) (minBufferSizeIn * numDeviceInputChannels * static_cast<int> (sizeof (int16)))));

                int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState);
                if (inputDeviceState > 0)
                {
                    isRunning = true;
                }
                else
                {
                     // failed to open the device
                    inputDevice.clear();
                    lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState);
                }
            }
        }

        if (isRunning)
        {
            if (outputDevice != nullptr)
                env->CallVoidMethod (outputDevice, AudioTrack.play);

            if (inputDevice != nullptr)
                env->CallVoidMethod (inputDevice, AudioRecord.startRecording);

            startThread (8);
        }
        else
        {
            closeDevices();
        }

        return lastError;
    }

    void close() override
    {
        if (isRunning)
        {
            stopThread (2000);
            isRunning = false;
            closeDevices();
        }
    }

    int getOutputLatencyInSamples() override             { return (minBufferSizeOut * 3) / 4; }
    int getInputLatencyInSamples() override              { return (minBufferSizeIn * 3) / 4; }
    bool isOpen() override                               { return isRunning; }
    int getCurrentBufferSizeSamples() override           { return actualBufferSize; }
    int getCurrentBitDepth() override                    { return 16; }
    double getCurrentSampleRate() override               { return sampleRate; }
    BigInteger getActiveOutputChannels() const override  { return activeOutputChans; }
    BigInteger getActiveInputChannels() const override   { return activeInputChans; }
    String getLastError() override                       { return lastError; }
    bool isPlaying() override                            { return isRunning && callback != 0; }

    int getXRunCount() const noexcept override
    {
        if (outputDevice != nullptr && getUnderrunCount != 0)
            return getEnv()->CallIntMethod (outputDevice, getUnderrunCount);

        return -1;
    }

    void start (AudioIODeviceCallback* newCallback) override
    {
        if (isRunning && callback != newCallback)
        {
            if (newCallback != nullptr)
                newCallback->audioDeviceAboutToStart (this);

            const ScopedLock sl (callbackLock);
            callback = newCallback;
        }
    }

    void stop() override
    {
        if (isRunning)
        {
            AudioIODeviceCallback* lastCallback;

            {
                const ScopedLock sl (callbackLock);
                lastCallback = callback;
                callback = nullptr;
            }

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

    void run() override
    {
        JNIEnv* env = getEnv();
        jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels));

        while (! threadShouldExit())
        {
            if (inputDevice != nullptr)
            {
                jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels);

                if (numRead < actualBufferSize * numDeviceInputChannels)
                {
                    DBG ("Audio read under-run! " << numRead);
                }

                jshort* const src = env->GetShortArrayElements (audioBuffer, 0);

                for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan)
                {
                    AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan));

                    if (chan < numDeviceInputChannels)
                    {
                        AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> s (src + chan, numDeviceInputChannels);
                        d.convertSamples (s, actualBufferSize);
                    }
                    else
                    {
                        d.clearSamples (actualBufferSize);
                    }
                }

                env->ReleaseShortArrayElements (audioBuffer, src, 0);
            }

            if (threadShouldExit())
                break;

            {
                const ScopedLock sl (callbackLock);

                if (callback != nullptr)
                {
                    callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels,
                                                     outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels,
                                                     actualBufferSize);
                }
                else
                {
                    outputChannelBuffer.clear();
                }
            }

            if (outputDevice != nullptr)
            {
                if (threadShouldExit())
                    break;

                jshort* const dest = env->GetShortArrayElements (audioBuffer, 0);

                for (int chan = 0; chan < numDeviceOutputChannels; ++chan)
                {
                    AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels);

                    const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1));
                    AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData);
                    d.convertSamples (s, actualBufferSize);
                }

                env->ReleaseShortArrayElements (audioBuffer, dest, 0);
                jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels);

                if (numWritten < actualBufferSize * numDeviceOutputChannels)
                {
                    DBG ("Audio write underrun! " << numWritten);
                }
            }
        }
    }

    int minBufferSizeOut, minBufferSizeIn;

private:
    //==============================================================================
    CriticalSection callbackLock;
    AudioIODeviceCallback* callback;
    jint sampleRate;
    int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable;
    int numClientOutputChannels, numDeviceOutputChannels;
    int actualBufferSize;
    bool isRunning;
    String lastError;
    BigInteger activeOutputChans, activeInputChans;
    GlobalRef outputDevice, inputDevice;
    AudioSampleBuffer inputChannelBuffer, outputChannelBuffer;
    jmethodID getUnderrunCount = 0;

    void closeDevices()
    {
        if (outputDevice != nullptr)
        {
            outputDevice.callVoidMethod (AudioTrack.stop);
            outputDevice.callVoidMethod (AudioTrack.release);
            outputDevice.clear();
        }

        if (inputDevice != nullptr)
        {
            inputDevice.callVoidMethod (AudioRecord.stop);
            inputDevice.callVoidMethod (AudioRecord.release);
            inputDevice.clear();
        }
    }

    JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice)
};

//==============================================================================
class AndroidAudioIODeviceType  : public AudioIODeviceType
{
public:
    AndroidAudioIODeviceType()  : AudioIODeviceType (javaAudioTypeName) {}

    //==============================================================================
    void scanForDevices() {}
    StringArray getDeviceNames (bool) const                             { return StringArray (javaAudioTypeName); }
    int getDefaultDeviceIndex (bool) const                              { return 0; }
    int getIndexOfDevice (AudioIODevice* device, bool) const            { return device != nullptr ? 0 : -1; }
    bool hasSeparateInputsAndOutputs() const                            { return false; }

    AudioIODevice* createDevice (const String& outputDeviceName,
                                 const String& inputDeviceName)
    {
        ScopedPointer<AndroidAudioIODevice> dev;

        if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
        {
            dev = new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
                                                                          : inputDeviceName);

            if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0)
                dev = nullptr;
        }

        return dev.release();
    }

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType)
};

//==============================================================================
extern bool isOpenSLAvailable();

AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android()
{
   #if JUCE_USE_ANDROID_OPENSLES
    if (isOpenSLAvailable())
        return nullptr;
   #endif

    return new AndroidAudioIODeviceType();
}

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   To use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \
 METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \
 METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
 METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
 METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \
 METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;")
 DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager")
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (start, "start", "()V" )\
 METHOD (stop, "stop", "()V") \
 METHOD (close, "close", "()V") \
 METHOD (sendMidi, "sendMidi", "([BII)V")
 DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort")
#undef JNI_CLASS_MEMBERS

//==============================================================================
class AndroidMidiInput
{
public:
    AndroidMidiInput (MidiInput* midiInput, int portIdx,
                      juce::MidiInputCallback* midiInputCallback, jobject deviceManager)
        : juceMidiInput (midiInput),
          callback (midiInputCallback),
          midiConcatenator (2048),
          javaMidiDevice (getEnv()->CallObjectMethod (deviceManager,
                                                      MidiDeviceManager.openMidiInputPortWithJuceIndex,
                                                      (jint) portIdx,
                                                      (jlong) this))
    {
    }

    ~AndroidMidiInput()
    {
        if (jobject d = javaMidiDevice.get())
        {
            getEnv()->CallVoidMethod (d, JuceMidiPort.close);
            javaMidiDevice.clear();
        }
    }

    bool isOpen() const noexcept
    {
        return javaMidiDevice != nullptr;
    }

    void start()
    {
        if (jobject d = javaMidiDevice.get())
            getEnv()->CallVoidMethod (d, JuceMidiPort.start);
    }

    void stop()
    {
        if (jobject d = javaMidiDevice.get())
            getEnv()->CallVoidMethod (d, JuceMidiPort.stop);

        callback = nullptr;
    }

    void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
    {
        jassert (byteArray != nullptr);
        jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr);

        HeapBlock<uint8> buffer (static_cast<size_t> (len));
        std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len));

        midiConcatenator.pushMidiData (buffer.get(),
                                       len, static_cast<double> (timestamp) * 1.0e-9,
                                       juceMidiInput, *callback);

        getEnv()->ReleaseByteArrayElements (byteArray, data, 0);
    }

private:
    MidiInput* juceMidiInput;
    MidiInputCallback* callback;
    MidiDataConcatenator midiConcatenator;
    GlobalRef javaMidiDevice;
};

//==============================================================================
class AndroidMidiOutput
{
public:
    AndroidMidiOutput (jobject midiDevice)
        : javaMidiDevice (midiDevice)
    {
    }

    ~AndroidMidiOutput()
    {
        if (jobject d = javaMidiDevice.get())
        {
            getEnv()->CallVoidMethod (d, JuceMidiPort.close);
            javaMidiDevice.clear();
        }
    }

    void send (jbyteArray byteArray, jint offset, jint len)
    {
        if (jobject d = javaMidiDevice.get())
            getEnv()->CallVoidMethod (d,
                                      JuceMidiPort.sendMidi,
                                      byteArray, offset, len);
    }

private:
    GlobalRef javaMidiDevice;
};

JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive,
                   void, (JNIEnv* env, jobject, jlong host, jbyteArray byteArray,
                          jint offset, jint count, jlong timestamp))
{
    // Java may create a Midi thread which JUCE doesn't know about and this callback may be
    // received on this thread. Java will have already created a JNI Env for this new thread,
    // which we need to tell Juce about
    setEnv (env);

    reinterpret_cast<AndroidMidiInput*> (host)->receive (byteArray, offset, count, timestamp);
}

//==============================================================================
class AndroidMidiDeviceManager
{
public:
    AndroidMidiDeviceManager()
        : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager))
    {
    }

    String getInputPortNameForJuceIndex (int idx)
    {
        if (jobject dm = deviceManager.get())
        {
            LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx));
            return juceString (string);
        }

        return {};
    }

    String getOutputPortNameForJuceIndex (int idx)
    {
        if (jobject dm = deviceManager.get())
        {
            LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx));
            return juceString (string);
        }

        return {};
    }

    StringArray getDevices (bool input)
    {
        if (jobject dm = deviceManager.get())
        {
            jobjectArray jDevices
                = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices
                                                                  : MidiDeviceManager.getJuceAndroidMidiOutputDevices);

            // Create a local reference as converting this
            // to a JUCE string will call into JNI
            LocalRef<jobjectArray> devices (jDevices);
            return javaStringArrayToJuce (devices);
        }

        return {};
    }

    AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback)
    {
        if (jobject dm = deviceManager.get())
        {
            ScopedPointer<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm));

            if (androidMidiInput->isOpen())
                return androidMidiInput.release();
        }

        return nullptr;
    }

    AndroidMidiOutput* openMidiOutputPortWithIndex (int idx)
    {
        if (jobject dm = deviceManager.get())
            if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx))
                return new AndroidMidiOutput (javaMidiPort);

        return nullptr;
    }

private:
    static StringArray javaStringArrayToJuce (jobjectArray jStrings)
    {
        StringArray retval;

        JNIEnv* env = getEnv();
        const int count = env->GetArrayLength (jStrings);

        for (int i = 0; i < count; ++i)
        {
            LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (jStrings, i));
            retval.add (juceString (string));
        }

        return retval;
    }

    GlobalRef deviceManager;
};

//==============================================================================
StringArray MidiOutput::getDevices()
{
    AndroidMidiDeviceManager manager;
    return manager.getDevices (false);
}

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

MidiOutput* MidiOutput::openDevice (int index)
{
    if (index < 0)
        return nullptr;

    AndroidMidiDeviceManager manager;

    String midiOutputName = manager.getOutputPortNameForJuceIndex (index);

    if (midiOutputName.isEmpty())
    {
        // you supplied an invalid device index!
        jassertfalse;
        return nullptr;
    }

    if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index))
    {
        MidiOutput* retval = new MidiOutput (midiOutputName);
        retval->internal = midiOutput;

        return retval;
    }

    return nullptr;
}

MidiOutput::~MidiOutput()
{
    stopBackgroundThread();

    delete reinterpret_cast<AndroidMidiOutput*> (internal);
}

void MidiOutput::sendMessageNow (const MidiMessage& message)
{
    if (AndroidMidiOutput* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal))
    {
        JNIEnv* env = getEnv();
        const int messageSize = message.getRawDataSize();

        LocalRef<jbyteArray> messageContent = LocalRef<jbyteArray> (env->NewByteArray (messageSize));
        jbyteArray content = messageContent.get();

        jbyte* rawBytes = env->GetByteArrayElements (content, nullptr);
        std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (messageSize));
        env->ReleaseByteArrayElements (content, rawBytes, 0);

        androidMidi->send (content, (jint) 0, (jint) messageSize);
    }
}

//==============================================================================
MidiInput::MidiInput (const String& nm)  : name (nm)
{
}

StringArray MidiInput::getDevices()
{
    AndroidMidiDeviceManager manager;
    return manager.getDevices (true);
}

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

MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback)
{
    if (index < 0)
        return nullptr;

    AndroidMidiDeviceManager manager;

    String midiInputName = manager.getInputPortNameForJuceIndex (index);

    if (midiInputName.isEmpty())
    {
        // you supplied an invalid device index!
        jassertfalse;
        return nullptr;
    }

    ScopedPointer<MidiInput> midiInput (new MidiInput (midiInputName));

    midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput, callback);

    return midiInput->internal != nullptr ? midiInput.release()
                                          : nullptr;
}

void MidiInput::start()
{
    if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
        mi->start();
}

void MidiInput::stop()
{
    if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
        mi->stop();
}

MidiInput::~MidiInput()
{
    delete reinterpret_cast<AndroidMidiInput*> (internal);
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   To use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

#ifndef JUCE_LOG_COREMIDI_ERRORS
 #define JUCE_LOG_COREMIDI_ERRORS 1
#endif

namespace CoreMidiHelpers
{
    static bool checkError (const OSStatus err, const int lineNum)
    {
        if (err == noErr)
            return true;

       #if JUCE_LOG_COREMIDI_ERRORS
        Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err));
       #endif

        ignoreUnused (lineNum);
        return false;
    }

    #undef CHECK_ERROR
    #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__)

    //==============================================================================
    struct ScopedCFString
    {
        ScopedCFString() noexcept : cfString (nullptr) {}
        ~ScopedCFString() noexcept  { if (cfString != nullptr) CFRelease (cfString); }

        CFStringRef cfString;
    };

    static String getMidiObjectName (MIDIObjectRef entity)
    {
        String result;
        CFStringRef str = nullptr;
        MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str);

        if (str != nullptr)
        {
            result = String::fromCFString (str);
            CFRelease (str);
        }

        return result;
    }

    static void enableSimulatorMidiSession()
    {
       #if TARGET_OS_SIMULATOR
        static bool hasEnabledNetworkSession = false;

        if (! hasEnabledNetworkSession)
        {
            MIDINetworkSession* session = [MIDINetworkSession defaultSession];
            session.enabled = YES;
            session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone;

            hasEnabledNetworkSession = true;
        }
       #endif
    }

    static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal)
    {
        String result (getMidiObjectName (endpoint));

        MIDIEntityRef entity = 0;  // NB: don't attempt to use nullptr for refs - it fails in some types of build.
        MIDIEndpointGetEntity (endpoint, &entity);

        if (entity == 0)
            return result; // probably virtual

        if (result.isEmpty())
            result = getMidiObjectName (entity);  // endpoint name is empty - try the entity

        // now consider the device's name
        MIDIDeviceRef device = 0;
        MIDIEntityGetDevice (entity, &device);

        if (device != 0)
        {
            const String deviceName (getMidiObjectName (device));

            if (deviceName.isNotEmpty())
            {
                // if an external device has only one entity, throw away
                // the endpoint name and just use the device name
                if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2)
                {
                    result = deviceName;
                }
                else if (! result.startsWithIgnoreCase (deviceName))
                {
                    // prepend the device name to the entity name
                    result = (deviceName + " " + result).trimEnd();
                }
            }
        }

        return result;
    }

    static String getConnectedEndpointName (MIDIEndpointRef endpoint)
    {
        String result;

        // Does the endpoint have connections?
        CFDataRef connections = nullptr;
        int numConnections = 0;

        MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections);

        if (connections != nullptr)
        {
            numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID);

            if (numConnections > 0)
            {
                const SInt32* pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections));

                for (int i = 0; i < numConnections; ++i, ++pid)
                {
                    MIDIUniqueID uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid);
                    MIDIObjectRef connObject;
                    MIDIObjectType connObjectType;
                    OSStatus err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType);

                    if (err == noErr)
                    {
                        String s;

                        if (connObjectType == kMIDIObjectType_ExternalSource
                             || connObjectType == kMIDIObjectType_ExternalDestination)
                        {
                            // Connected to an external device's endpoint (10.3 and later).
                            s = getEndpointName (static_cast<MIDIEndpointRef> (connObject), true);
                        }
                        else
                        {
                            // Connected to an external device (10.2) (or something else, catch-all)
                            s = getMidiObjectName (connObject);
                        }

                        if (s.isNotEmpty())
                        {
                            if (result.isNotEmpty())
                                result += ", ";

                            result += s;
                        }
                    }
                }
            }

            CFRelease (connections);
        }

        if (result.isEmpty())  // Here, either the endpoint had no connections, or we failed to obtain names for them.
            result = getEndpointName (endpoint, false);

        return result;
    }

    static void setUniqueIdForMidiPort (MIDIObjectRef device, const String& portName, bool isInput)
    {
        String portUniqueId;
       #if defined (JucePlugin_CFBundleIdentifier)
        portUniqueId = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier);
       #else
        File appBundle (File::getSpecialLocation (File::currentApplicationFile));
        CFURLRef bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, appBundle.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true);
        if (bundleURL != nullptr)
        {
            CFBundleRef bundleRef = CFBundleCreate (kCFAllocatorDefault, bundleURL);
            CFRelease (bundleURL);

            if (bundleRef != nullptr)
            {
                if (auto bundleId = CFBundleGetIdentifier (bundleRef))
                    portUniqueId = String::fromCFString (bundleId);

                CFRelease (bundleRef);
            }
        }
       #endif

        if (portUniqueId.isNotEmpty())
        {
            portUniqueId += (String ("." + portName + String (isInput ? ".input" : ".output")));

            CHECK_ERROR (MIDIObjectSetStringProperty (device, kMIDIPropertyUniqueID, portUniqueId.toCFString()));
        }
    }

    static StringArray findDevices (const bool forInput)
    {
        // It seems that OSX can be a bit picky about the thread that's first used to
        // search for devices. It's safest to use the message thread for calling this.
        jassert (MessageManager::getInstance()->isThisTheMessageThread());

        enableSimulatorMidiSession();

        const ItemCount num = forInput ? MIDIGetNumberOfSources()
                                       : MIDIGetNumberOfDestinations();
        StringArray s;

        for (ItemCount i = 0; i < num; ++i)
        {
            MIDIEndpointRef dest = forInput ? MIDIGetSource (i)
                                            : MIDIGetDestination (i);
            String name;

            if (dest != 0)
                name = getConnectedEndpointName (dest);

            if (name.isEmpty())
                name = "<error>";

            s.add (name);
        }

        return s;
    }

    static void globalSystemChangeCallback (const MIDINotification*, void*)
    {
        // TODO.. Should pass-on this notification..
    }

    static String getGlobalMidiClientName()
    {
        if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
            return app->getApplicationName();

        return "JUCE";
    }

    static MIDIClientRef getGlobalMidiClient()
    {
        static MIDIClientRef globalMidiClient = 0;

        if (globalMidiClient == 0)
        {
            // Since OSX 10.6, the MIDIClientCreate function will only work
            // correctly when called from the message thread!
            jassert (MessageManager::getInstance()->isThisTheMessageThread());

            enableSimulatorMidiSession();

            CoreMidiHelpers::ScopedCFString name;
            name.cfString = getGlobalMidiClientName().toCFString();
            CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient));
        }

        return globalMidiClient;
    }

    //==============================================================================
    class MidiPortAndEndpoint
    {
    public:
        MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept
            : port (p), endPoint (ep)
        {
        }

        ~MidiPortAndEndpoint() noexcept
        {
            if (port != 0)
                MIDIPortDispose (port);

            if (port == 0 && endPoint != 0) // if port == nullptr, it means we created the endpoint, so it's safe to delete it
                MIDIEndpointDispose (endPoint);
        }

        void send (const MIDIPacketList* const packets) noexcept
        {
            if (port != 0)
                MIDISend (port, endPoint, packets);
            else
                MIDIReceived (endPoint, packets);
        }

        MIDIPortRef port;
        MIDIEndpointRef endPoint;
    };

    //==============================================================================
    class MidiPortAndCallback;
    CriticalSection callbackLock;
    Array<MidiPortAndCallback*> activeCallbacks;

    class MidiPortAndCallback
    {
    public:
        MidiPortAndCallback (MidiInputCallback& cb)
            : input (nullptr), active (false), callback (cb), concatenator (2048)
        {
        }

        ~MidiPortAndCallback()
        {
            active = false;

            {
                const ScopedLock sl (callbackLock);
                activeCallbacks.removeFirstMatchingValue (this);
            }

            if (portAndEndpoint != 0 && portAndEndpoint->port != 0)
                CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endPoint));
        }

        void handlePackets (const MIDIPacketList* const pktlist)
        {
            const double time = Time::getMillisecondCounterHiRes() * 0.001;

            const ScopedLock sl (callbackLock);
            if (activeCallbacks.contains (this) && active)
            {
                const MIDIPacket* packet = &pktlist->packet[0];

                for (unsigned int i = 0; i < pktlist->numPackets; ++i)
                {
                    concatenator.pushMidiData (packet->data, (int) packet->length, time,
                                               input, callback);

                    packet = MIDIPacketNext (packet);
                }
            }
        }

        MidiInput* input;
        ScopedPointer<MidiPortAndEndpoint> portAndEndpoint;
        volatile bool active;

    private:
        MidiInputCallback& callback;
        MidiDataConcatenator concatenator;
    };

    static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/)
    {
        static_cast<MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist);
    }
}

//==============================================================================
StringArray MidiOutput::getDevices()        { return CoreMidiHelpers::findDevices (false); }
int MidiOutput::getDefaultDeviceIndex()     { return 0; }

MidiOutput* MidiOutput::openDevice (int index)
{
    MidiOutput* mo = nullptr;

    if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations()))
    {
        MIDIEndpointRef endPoint = MIDIGetDestination ((ItemCount) index);

        CoreMidiHelpers::ScopedCFString pname;

        if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString)))
        {
            MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient();
            MIDIPortRef port;
            String deviceName = CoreMidiHelpers::getConnectedEndpointName (endPoint);

            if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port)))
            {
                mo = new MidiOutput (deviceName);
                mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint);
            }
        }
    }

    return mo;
}

MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
{
    MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient();
    MIDIEndpointRef endPoint;

    CoreMidiHelpers::ScopedCFString name;
    name.cfString = deviceName.toCFString();

    if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint)))
    {
        CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false);

        MidiOutput* mo = new MidiOutput (deviceName);
        mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint);
        return mo;
    }

    return nullptr;
}

MidiOutput::~MidiOutput()
{
    stopBackgroundThread();

    delete static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal);
}

void MidiOutput::sendMessageNow (const MidiMessage& message)
{
   #if JUCE_IOS
    const MIDITimeStamp timeStamp = mach_absolute_time();
   #else
    const MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
   #endif

    HeapBlock<MIDIPacketList> allocatedPackets;
    MIDIPacketList stackPacket;
    MIDIPacketList* packetToSend = &stackPacket;
    const size_t dataSize = (size_t) message.getRawDataSize();

    if (message.isSysEx())
    {
        const int maxPacketSize = 256;
        int pos = 0, bytesLeft = (int) dataSize;
        const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize;
        allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1);
        packetToSend = allocatedPackets;
        packetToSend->numPackets = (UInt32) numPackets;

        MIDIPacket* p = packetToSend->packet;

        for (int i = 0; i < numPackets; ++i)
        {
            p->timeStamp = timeStamp;
            p->length = (UInt16) jmin (maxPacketSize, bytesLeft);
            memcpy (p->data, message.getRawData() + pos, p->length);
            pos += p->length;
            bytesLeft -= p->length;
            p = MIDIPacketNext (p);
        }
    }
    else if (dataSize < 65536) // max packet size
    {
        const size_t stackCapacity = sizeof (stackPacket.packet->data);

        if (dataSize > stackCapacity)
        {
            allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1);
            packetToSend = allocatedPackets;
        }

        packetToSend->numPackets = 1;
        MIDIPacket& p = *(packetToSend->packet);
        p.timeStamp = timeStamp;
        p.length = (UInt16) dataSize;
        memcpy (p.data, message.getRawData(), dataSize);
    }
    else
    {
        jassertfalse; // packet too large to send!
        return;
    }

    static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend);
}

//==============================================================================
StringArray MidiInput::getDevices()     { return CoreMidiHelpers::findDevices (true); }
int MidiInput::getDefaultDeviceIndex()  { return 0; }

MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
{
    jassert (callback != nullptr);

    using namespace CoreMidiHelpers;
    MidiInput* newInput = nullptr;

    if (isPositiveAndBelow (index, MIDIGetNumberOfSources()))
    {
        if (MIDIEndpointRef endPoint = MIDIGetSource ((ItemCount) index))
        {
            ScopedCFString name;

            if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString)))
            {
                if (MIDIClientRef client = getGlobalMidiClient())
                {
                    MIDIPortRef port;
                    ScopedPointer<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback));

                    if (CHECK_ERROR (MIDIInputPortCreate (client, name.cfString, midiInputProc, mpc, &port)))
                    {
                        if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, nullptr)))
                        {
                            mpc->portAndEndpoint = new MidiPortAndEndpoint (port, endPoint);

                            newInput = new MidiInput (getDevices() [index]);
                            mpc->input = newInput;
                            newInput->internal = mpc;

                            const ScopedLock sl (callbackLock);
                            activeCallbacks.add (mpc.release());
                        }
                        else
                        {
                            CHECK_ERROR (MIDIPortDispose (port));
                        }
                    }
                }
            }
        }
    }

    return newInput;
}

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

    using namespace CoreMidiHelpers;
    MidiInput* mi = nullptr;

    if (MIDIClientRef client = getGlobalMidiClient())
    {
        ScopedPointer<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback));
        mpc->active = false;

        MIDIEndpointRef endPoint;
        ScopedCFString name;
        name.cfString = deviceName.toCFString();

        if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc, &endPoint)))
        {
            CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, true);

            mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint);

            mi = new MidiInput (deviceName);
            mpc->input = mi;
            mi->internal = mpc;

            const ScopedLock sl (callbackLock);
            activeCallbacks.add (mpc.release());
        }
    }

    return mi;
}

MidiInput::MidiInput (const String& nm)  : name (nm)
{
}

MidiInput::~MidiInput()
{
    delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal);
}

void MidiInput::start()
{
    const ScopedLock sl (CoreMidiHelpers::callbackLock);
    static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true;
}

void MidiInput::stop()
{
    const ScopedLock sl (CoreMidiHelpers::callbackLock);
    static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false;
}

#undef CHECK_ERROR

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   To use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

namespace
{

#ifndef JUCE_ALSA_LOGGING
 #define JUCE_ALSA_LOGGING 0
#endif

#if JUCE_ALSA_LOGGING
 #define JUCE_ALSA_LOG(dbgtext)   { juce::String tempDbgBuf ("ALSA: "); tempDbgBuf << dbgtext; Logger::writeToLog (tempDbgBuf); DBG (tempDbgBuf); }
 #define JUCE_CHECKED_RESULT(x)   (logErrorMessage (x, __LINE__))

 static int logErrorMessage (int err, int lineNum)
 {
    if (err < 0)
        JUCE_ALSA_LOG ("Error: line " << lineNum << ": code " << err << " (" << snd_strerror (err) << ")");

    return err;
 }
#else
 #define JUCE_ALSA_LOG(x)         {}
 #define JUCE_CHECKED_RESULT(x)   (x)
#endif

#define JUCE_ALSA_FAILED(x)  failed (x)

static void getDeviceSampleRates (snd_pcm_t* handle, Array<double>& 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, (unsigned int) ratesToTry[i], 0) == 0)
        {
            rates.addIfNotAlreadyThere ((double) ratesToTry[i]);
        }
    }
}

static 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);

        JUCE_ALSA_LOG ("getDeviceNumChannels: " << (int) *minChans << " " << (int) *maxChans);

        // some virtual devices (dmix for example) report 10000 channels , we have to clamp these values
        *maxChans = jmin (*maxChans, 256u);
        *minChans = jmin (*minChans, *maxChans);
    }
    else
    {
        JUCE_ALSA_LOG ("getDeviceNumChannels failed");
    }
}

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

    if (deviceID.isEmpty())
        return;

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

    snd_pcm_info_t* info;
    snd_pcm_info_alloca (&info);

    if (testOutput)
    {
        snd_pcm_t* pcmHandle;

        if (JUCE_CHECKED_RESULT (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 (JUCE_CHECKED_RESULT (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);
        }
    }
}

static void ensureMinimumNumBitsSet (BigInteger& chans, int minNumChans)
{
    int i = 0;

    while (chans.countNumberOfSetBits() < minNumChans)
        chans.setBit (i++);
}

static void silentErrorHandler (const char*, int, const char*, int, const char*,...) {}

//==============================================================================
class ALSADevice
{
public:
    ALSADevice (const String& devID, bool forInput)
        : handle (nullptr),
          bitDepth (16),
          numChannelsRunning (0),
          latency (0),
          deviceID (devID),
          isInput (forInput),
          isInterleaved (true)
    {
        JUCE_ALSA_LOG ("snd_pcm_open (" << deviceID.toUTF8().getAddress() << ", forInput=" << (int) forInput << ")");

        int err = snd_pcm_open (&handle, deviceID.toUTF8(),
                                forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
                                SND_PCM_ASYNC);
        if (err < 0)
        {
            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 << ")";

            JUCE_ALSA_LOG ("snd_pcm_open failed; " << error);
        }
    }

    ~ALSADevice()
    {
        closeNow();
    }

    void closeNow()
    {
        if (handle != nullptr)
        {
            snd_pcm_close (handle);
            handle = nullptr;
        }
    }

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

        JUCE_ALSA_LOG ("ALSADevice::setParameters(" << deviceID << ", "
                         << (int) 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, onlyUseLower24Bits = 1 << 18 };

        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_S24_LE,     32 | isLittleEndianBit | onlyUseLower24Bits,
                                     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)
            {
                const int type = formatsToTry [i + 1];
                bitDepth = type & 255;

                converter = createConverter (isInput, bitDepth,
                                             (type & isFloatBit) != 0,
                                             (type & isLittleEndianBit) != 0,
                                             (type & onlyUseLower24Bits) != 0,
                                             numChannels,
                                             isInterleaved);
                break;
            }
        }

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

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

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

        snd_pcm_uframes_t frames = 0;

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

        JUCE_ALSA_LOG ("frames: " << (int) frames << ", periods: " << (int) periods
                          << ", samplesPerPeriod: " << (int) samplesPerPeriod);

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

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

       #if JUCE_ALSA_LOGGING
        // 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);
       #endif

        numChannelsRunning = numChannels;

        return true;
    }

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

        if (isInterleaved)
        {
            scratch.ensureSize ((size_t) ((int) 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(), (snd_pcm_uframes_t) numSamples);
        }
        else
        {
            for (int i = 0; i < numChannelsRunning; ++i)
                converter->convertSamples (data[i], data[i], numSamples);

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

        if (numDone < 0)
        {
            if (numDone == -(EPIPE))
                underrunCount++;

            if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) numDone, 1 /* silent */)))
                return false;
        }

        if (numDone < numSamples)
            JUCE_ALSA_LOG ("Did not write all samples: numDone: " << numDone << ", numSamples: " << numSamples);

        return true;
    }

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

        if (isInterleaved)
        {
            scratch.ensureSize ((size_t) ((int) 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(), (snd_pcm_uframes_t) numSamples);

            if (num < 0)
            {
                if (num == -(EPIPE))
                    overrunCount++;

                if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
                    return false;
            }

            if (num < numSamples)
                JUCE_ALSA_LOG ("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, (snd_pcm_uframes_t) numSamples);

            if (num < 0)
            {
                if (num == -(EPIPE))
                    overrunCount++;

                if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
                    return false;
            }

            if (num < numSamples)
                JUCE_ALSA_LOG ("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;
    int underrunCount = 0, overrunCount = 0;

private:
    //==============================================================================
    String deviceID;
    const bool isInput;
    bool isInterleaved;
    MemoryBlock scratch;
    ScopedPointer<AudioData::Converter> converter;

    //==============================================================================
    template <class SampleType>
    struct ConverterHelper
    {
        static AudioData::Converter* createConverter (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels, bool interleaved)
        {
            if (interleaved)
                return create<AudioData::Interleaved> (forInput, isLittleEndian, numInterleavedChannels);

            return create<AudioData::NonInterleaved> (forInput, isLittleEndian, numInterleavedChannels);
        }

    private:
        template <class InterleavedType>
        static AudioData::Converter* create (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, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);

                return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);
            }

            typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SourceType;

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

            return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::NonConst>> (1, numInterleavedChannels);
        }
    };

    static AudioData::Converter* createConverter (bool forInput, int bitDepth,
                                                  bool isFloat, bool isLittleEndian, bool useOnlyLower24Bits,
                                                  int numInterleavedChannels,
                                                  bool interleaved)
    {
        JUCE_ALSA_LOG ("format: bitDepth=" << bitDepth << ", isFloat=" << (int) isFloat
                        << ", isLittleEndian=" << (int) isLittleEndian << ", numChannels=" << numInterleavedChannels);

        if (isFloat)         return ConverterHelper <AudioData::Float32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
        if (bitDepth == 16)  return ConverterHelper <AudioData::Int16>  ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
        if (bitDepth == 24)  return ConverterHelper <AudioData::Int24>  ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);

        jassert (bitDepth == 32);

        if (useOnlyLower24Bits)
            return ConverterHelper <AudioData::Int24in32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);

        return ConverterHelper <AudioData::Int32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
    }

    //==============================================================================
    bool failed (const int errorNum)
    {
        if (errorNum >= 0)
            return false;

        error = snd_strerror (errorNum);
        JUCE_ALSA_LOG ("ALSA error: " << error);
        return true;
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSADevice)
};

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

    ~ALSAThread()
    {
        close();
    }

    void open (BigInteger inputChannels,
               BigInteger outputChannels,
               const double newSampleRate,
               const int newBufferSize)
    {
        close();

        error.clear();
        sampleRate = newSampleRate;
        bufferSize = newBufferSize;

        int maxInputsRequested = inputChannels.getHighestBit() + 1;
        maxInputsRequested = jmax ((int) minChansIn, jmin ((int) maxChansIn, maxInputsRequested));

        inputChannelBuffer.setSize (maxInputsRequested, bufferSize);
        inputChannelBuffer.clear();
        inputChannelDataForCallback.clear();
        currentInputChans.clear();

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

        ensureMinimumNumBitsSet (outputChannels, (int) minChansOut);

        int maxOutputsRequested = outputChannels.getHighestBit() + 1;
        maxOutputsRequested = jmax ((int) minChansOut, jmin ((int) maxChansOut, maxOutputsRequested));

        outputChannelBuffer.setSize (maxOutputsRequested, bufferSize);
        outputChannelBuffer.clear();
        outputChannelDataForCallback.clear();
        currentOutputChans.clear();

        if (outputChannels.getHighestBit() >= 0)
        {
            for (int i = 0; i < maxOutputsRequested; ++i)
            {
                if (outputChannels[i])
                {
                    outputChannelDataForCallback.add (outputChannelBuffer.getWritePointer (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;
            }

            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;
            }

            ensureMinimumNumBitsSet (currentInputChans, (int) minChansIn);

            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 && JUCE_ALSA_FAILED (snd_pcm_prepare (inputDevice->handle)))
            return;

        if (outputDevice != nullptr && JUCE_ALSA_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();

            const int callbacksToStop = numCallbacks;

            if ((! waitForThreadToExit (400)) && audioIoInProgress && numCallbacks == callbacksToStop)
            {
                JUCE_ALSA_LOG ("Thread is stuck in i/o.. Is pulseaudio suspended?");

                if (outputDevice != nullptr) outputDevice->closeNow();
                if (inputDevice != nullptr) 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() override
    {
        while (! threadShouldExit())
        {
            if (inputDevice != nullptr && inputDevice->handle != nullptr)
            {
                if (outputDevice == nullptr || outputDevice->handle == nullptr)
                {
                    JUCE_ALSA_FAILED (snd_pcm_wait (inputDevice->handle, 2000));

                    if (threadShouldExit())
                        break;

                    snd_pcm_sframes_t avail = snd_pcm_avail_update (inputDevice->handle);

                    if (avail < 0)
                        JUCE_ALSA_FAILED (snd_pcm_recover (inputDevice->handle, (int) avail, 0));
                }

                audioIoInProgress = true;

                if (! inputDevice->readFromInputDevice (inputChannelBuffer, bufferSize))
                {
                    JUCE_ALSA_LOG ("Read failure");
                    break;
                }

                audioIoInProgress = false;
            }

            if (threadShouldExit())
                break;

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

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

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

                if (threadShouldExit())
                    break;

                snd_pcm_sframes_t avail = snd_pcm_avail_update (outputDevice->handle);

                if (avail < 0)
                    JUCE_ALSA_FAILED (snd_pcm_recover (outputDevice->handle, (int) avail, 0));

                audioIoInProgress = true;

                if (! outputDevice->writeToOutputDevice (outputChannelBuffer, bufferSize))
                {
                    JUCE_ALSA_LOG ("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;
    }

    int getXRunCount() const noexcept
    {
        int result = 0;

        if (outputDevice != nullptr)
            result += outputDevice->underrunCount;

        if (inputDevice != nullptr)
            result += inputDevice->overrunCount;

        return result;
    }

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

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

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

    CriticalSection callbackLock;

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

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

    bool failed (const int errorNum)
    {
        if (errorNum >= 0)
            return false;

        error = snd_strerror (errorNum);
        JUCE_ALSA_LOG ("ALSA error: " << error);
        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);

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

        for (unsigned int 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& deviceTypeName,
                       const String& inputDeviceID,
                       const String& outputDeviceID)
        : AudioIODevice (deviceName, deviceTypeName),
          inputId (inputDeviceID),
          outputId (outputDeviceID),
          isOpen_ (false),
          isStarted (false),
          internal (inputDeviceID, outputDeviceID)
    {
    }

    ~ALSAAudioIODevice()
    {
        close();
    }

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

    Array<double> getAvailableSampleRates() override        { return internal.sampleRates; }

    Array<int> getAvailableBufferSizes() override
    {
        Array<int> r;
        int n = 16;

        for (int i = 0; i < 50; ++i)
        {
            r.add (n);
            n += n < 64 ? 16
                        : (n < 512 ? 32
                                   : (n < 1024 ? 64
                                               : (n < 2048 ? 128 : 256)));
        }

        return r;
    }

    int getDefaultBufferSize() override                      { return 512; }

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

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

        if (sampleRate <= 0)
        {
            for (int i = 0; i < internal.sampleRates.size(); ++i)
            {
                double rate = internal.sampleRates[i];

                if (rate >= 44100)
                {
                    sampleRate = rate;
                    break;
                }
            }
        }

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

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

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

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

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

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

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

    int getXRunCount() const noexcept override       { return internal.getXRunCount(); }

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

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

        internal.setCallback (callback);

        isStarted = (callback != nullptr);
    }

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

        start (nullptr);

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

    String inputId, outputId;

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

//==============================================================================
class ALSAAudioIODeviceType  : public AudioIODeviceType
{
public:
    ALSAAudioIODeviceType (bool onlySoundcards, const String &deviceTypeName)
        : AudioIODeviceType (deviceTypeName),
          hasScanned (false),
          listOnlySoundcards (onlySoundcards)
    {
       #if ! JUCE_ALSA_LOGGING
        snd_lib_error_set_handler (&silentErrorHandler);
       #endif
    }

    ~ALSAAudioIODeviceType()
    {
       #if ! JUCE_ALSA_LOGGING
        snd_lib_error_set_handler (nullptr);
       #endif

        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();

        JUCE_ALSA_LOG ("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
    {
        jassert (hasScanned); // need to call scanForDevices() before doing this

        const int idx = (forInput ? inputIds : outputIds).indexOf ("default");
        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

        if (ALSAAudioIODevice* d = dynamic_cast<ALSAAudioIODevice*> (device))
            return asInput ? inputIds.indexOf (d->inputId)
                           : outputIds.indexOf (d->outputId);

        return -1;
    }

    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<double> 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)
        {
            JUCE_ALSA_LOG ("testDevice: '" << id.toUTF8().getAddress() << "' -> isInput: "
                            << (int) isInput << ", isOutput: " << (int) isOutput);

            if (isInput)
            {
                inputNames.add (inputName);
                inputIds.add (id);
            }

            if (isOutput)
            {
                outputNames.add (outputName);
                outputIds.add (id);
            }

            return isInput || isOutput;
        }

        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 (JUCE_CHECKED_RESULT (snd_ctl_open (&handle, ("hw:" + String (cardNum)).toUTF8(), SND_CTL_NONBLOCK)) >= 0)
            {
                if (JUCE_CHECKED_RESULT (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, (unsigned int) device);

                        for (unsigned 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);
                            const bool isInput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);

                            snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_PLAYBACK);
                            const 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 << "," << (int) subDevice;
                                name << cardName << ", " << snd_pcm_info_get_name (pcmInfo)
                                     << " {" <<  snd_pcm_info_get_subdevice_name (pcmInfo) << "}";
                            }

                            JUCE_ALSA_LOG ("Soundcard ID: " << id << ", name: '" << name
                                            << ", isInput:"  << (int) isInput
                                            << ", isOutput:" << (int) isOutput << "\n");

                            if (isInput)
                            {
                                inputNames.add (name);
                                inputIds.add (id);
                            }

                            if (isOutput)
                            {
                                outputNames.add (name);
                                outputIds.add (id);
                            }
                        }
                    }
                }

                JUCE_CHECKED_RESULT (snd_ctl_close (handle));
            }
        }
    }

    /* Enumerates all ALSA output devices (as output by the command aplay -L)
       Does not try to open the devices (with "testDevice" for example),
       so that it also finds devices that are busy and not yet available.
    */
    void enumerateAlsaPCMDevices()
    {
        void** hints = nullptr;

        if (JUCE_CHECKED_RESULT (snd_device_name_hint (-1, "pcm", &hints)) == 0)
        {
            for (char** h = (char**) hints; *h; ++h)
            {
                const String id (hintToString (*h, "NAME"));
                const String description (hintToString (*h, "DESC"));
                const String ioid (hintToString (*h, "IOID"));

                JUCE_ALSA_LOG ("ID: " << id << "; desc: " << description << "; ioid: " << ioid);

                String ss = id.fromFirstOccurrenceOf ("=", false, false)
                              .upToFirstOccurrenceOf (",", false, false);

                if (id.isEmpty()
                     || id.startsWith ("default:") || id.startsWith ("sysdefault:")
                     || id.startsWith ("plughw:") || id == "null")
                    continue;

                String name (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, but
                // opening dmix as input, or dsnoop as output will trigger errors..
                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 = outputIds.indexOf ("pulse");
        outputIds.move (idx, 0);
        outputNames.move (idx, 0);

        idx = inputIds.indexOf ("pulse");
        inputIds.move (idx, 0);
        inputNames.move (idx, 0);

        idx = outputIds.indexOf ("default");
        outputIds.move (idx, 0);
        outputNames.move (idx, 0);

        idx = inputIds.indexOf ("default");
        inputIds.move (idx, 0);
        inputNames.move (idx, 0);
    }

    static String hintToString (const void* hints, const char* type)
    {
        char* const hint = snd_device_name_get_hint (hints, type);
        const String s (String::fromUTF8 (hint));
        ::free (hint);
        return 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();
}

} // namespace juce