upgrade to JUCE 5.4.3. Remove (probably) unused JUCE modules. Remove VST2 target (it's been end-of-life'd by Steinberg and by JUCE)
This commit is contained in:
		
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -33,79 +33,68 @@ namespace juce
 | 
			
		||||
class MidiDataConcatenator
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    MidiDataConcatenator (int initialBufferSize)
 | 
			
		||||
        : pendingData ((size_t) initialBufferSize)
 | 
			
		||||
        : pendingSysexData ((size_t) initialBufferSize)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void reset()
 | 
			
		||||
    {
 | 
			
		||||
        pendingBytes = 0;
 | 
			
		||||
        runningStatus = 0;
 | 
			
		||||
        pendingDataTime = 0;
 | 
			
		||||
        currentMessageLen = 0;
 | 
			
		||||
        pendingSysexSize = 0;
 | 
			
		||||
        pendingSysexTime = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename UserDataType, typename CallbackType>
 | 
			
		||||
    void pushMidiData (const void* inputData, int numBytes, double time,
 | 
			
		||||
                       UserDataType* input, CallbackType& callback)
 | 
			
		||||
    {
 | 
			
		||||
        const uint8* d = static_cast<const uint8*> (inputData);
 | 
			
		||||
        auto d = static_cast<const uint8*> (inputData);
 | 
			
		||||
 | 
			
		||||
        while (numBytes > 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (pendingBytes > 0 || d[0] == 0xf0)
 | 
			
		||||
            auto nextByte = *d;
 | 
			
		||||
 | 
			
		||||
            if (pendingSysexSize != 0 || nextByte == 0xf0)
 | 
			
		||||
            {
 | 
			
		||||
                processSysex (d, numBytes, time, input, callback);
 | 
			
		||||
                runningStatus = 0;
 | 
			
		||||
                currentMessageLen = 0;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ++d;
 | 
			
		||||
            --numBytes;
 | 
			
		||||
 | 
			
		||||
            if (isRealtimeMessage (nextByte))
 | 
			
		||||
            {
 | 
			
		||||
                callback.handleIncomingMidiMessage (input, MidiMessage (nextByte, time));
 | 
			
		||||
                // These can be embedded in the middle of a normal message, so we won't
 | 
			
		||||
                // reset the currentMessageLen here.
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (isInitialByte (nextByte))
 | 
			
		||||
            {
 | 
			
		||||
                currentMessage[0] = nextByte;
 | 
			
		||||
                currentMessageLen = 1;
 | 
			
		||||
            }
 | 
			
		||||
            else if (currentMessageLen > 0 && currentMessageLen < 3)
 | 
			
		||||
            {
 | 
			
		||||
                currentMessage[currentMessageLen++] = nextByte;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                int len = 0;
 | 
			
		||||
                uint8 data[3];
 | 
			
		||||
                // message is too long or invalid MIDI - abandon it and start again with the next byte
 | 
			
		||||
                currentMessageLen = 0;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                while (numBytes > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    // If there's a realtime message embedded in the middle of
 | 
			
		||||
                    // the normal message, handle it now..
 | 
			
		||||
                    if (*d >= 0xf8 && *d <= 0xfe)
 | 
			
		||||
                    {
 | 
			
		||||
                        callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time));
 | 
			
		||||
                        --numBytes;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        if (len == 0 && *d < 0x80 && runningStatus >= 0x80)
 | 
			
		||||
                            data[len++] = runningStatus;
 | 
			
		||||
            auto expectedLength = MidiMessage::getMessageLengthFromFirstByte (currentMessage[0]);
 | 
			
		||||
 | 
			
		||||
                        data[len++] = *d++;
 | 
			
		||||
                        --numBytes;
 | 
			
		||||
 | 
			
		||||
                        const uint8 firstByte = data[0];
 | 
			
		||||
 | 
			
		||||
                        if (firstByte < 0x80 || firstByte == 0xf7)
 | 
			
		||||
                        {
 | 
			
		||||
                            len = 0;
 | 
			
		||||
                            break;   // ignore this malformed MIDI message..
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (len >= MidiMessage::getMessageLengthFromFirstByte (firstByte))
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (len > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    int used = 0;
 | 
			
		||||
                    const MidiMessage m (data, len, used, 0, time);
 | 
			
		||||
 | 
			
		||||
                    if (used <= 0)
 | 
			
		||||
                        break; // malformed message..
 | 
			
		||||
 | 
			
		||||
                    jassert (used == len);
 | 
			
		||||
                    callback.handleIncomingMidiMessage (input, m);
 | 
			
		||||
                    runningStatus = data[0];
 | 
			
		||||
                }
 | 
			
		||||
            if (expectedLength == currentMessageLen)
 | 
			
		||||
            {
 | 
			
		||||
                callback.handleIncomingMidiMessage (input, MidiMessage (currentMessage, expectedLength, time));
 | 
			
		||||
                currentMessageLen = 1; // reset, but leave the first byte to use as the running status byte
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -117,22 +106,22 @@ private:
 | 
			
		||||
    {
 | 
			
		||||
        if (*d == 0xf0)
 | 
			
		||||
        {
 | 
			
		||||
            pendingBytes = 0;
 | 
			
		||||
            pendingDataTime = time;
 | 
			
		||||
            pendingSysexSize = 0;
 | 
			
		||||
            pendingSysexTime = time;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pendingData.ensureSize ((size_t) (pendingBytes + numBytes), false);
 | 
			
		||||
        uint8* totalMessage = static_cast<uint8*> (pendingData.getData());
 | 
			
		||||
        uint8* dest = totalMessage + pendingBytes;
 | 
			
		||||
        pendingSysexData.ensureSize ((size_t) (pendingSysexSize + numBytes), false);
 | 
			
		||||
        auto totalMessage = static_cast<uint8*> (pendingSysexData.getData());
 | 
			
		||||
        auto dest = totalMessage + pendingSysexSize;
 | 
			
		||||
 | 
			
		||||
        do
 | 
			
		||||
        {
 | 
			
		||||
            if (pendingBytes > 0 && *d >= 0x80)
 | 
			
		||||
            if (pendingSysexSize > 0 && isInitialByte (*d))
 | 
			
		||||
            {
 | 
			
		||||
                if (*d == 0xf7)
 | 
			
		||||
                {
 | 
			
		||||
                    *dest++ = *d++;
 | 
			
		||||
                    ++pendingBytes;
 | 
			
		||||
                    ++pendingSysexSize;
 | 
			
		||||
                    --numBytes;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
@ -145,7 +134,7 @@ private:
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    pendingBytes = 0;
 | 
			
		||||
                    pendingSysexSize = 0;
 | 
			
		||||
                    int used = 0;
 | 
			
		||||
                    const MidiMessage m (d, numBytes, used, 0, time);
 | 
			
		||||
 | 
			
		||||
@ -162,30 +151,35 @@ private:
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                *dest++ = *d++;
 | 
			
		||||
                ++pendingBytes;
 | 
			
		||||
                ++pendingSysexSize;
 | 
			
		||||
                --numBytes;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        while (numBytes > 0);
 | 
			
		||||
 | 
			
		||||
        if (pendingBytes > 0)
 | 
			
		||||
        if (pendingSysexSize > 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (totalMessage [pendingBytes - 1] == 0xf7)
 | 
			
		||||
            if (totalMessage [pendingSysexSize - 1] == 0xf7)
 | 
			
		||||
            {
 | 
			
		||||
                callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingBytes, pendingDataTime));
 | 
			
		||||
                pendingBytes = 0;
 | 
			
		||||
                callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingSysexSize, pendingSysexTime));
 | 
			
		||||
                pendingSysexSize = 0;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                callback.handlePartialSysexMessage (input, totalMessage, pendingBytes, pendingDataTime);
 | 
			
		||||
                callback.handlePartialSysexMessage (input, totalMessage, pendingSysexSize, pendingSysexTime);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MemoryBlock pendingData;
 | 
			
		||||
    double pendingDataTime = 0;
 | 
			
		||||
    int pendingBytes = 0;
 | 
			
		||||
    uint8 runningStatus = 0;
 | 
			
		||||
    static bool isRealtimeMessage (uint8 byte)  { return byte >= 0xf8 && byte <= 0xfe; }
 | 
			
		||||
    static bool isInitialByte (uint8 byte)      { return byte >= 0x80 && byte != 0xf7; }
 | 
			
		||||
 | 
			
		||||
    uint8 currentMessage[3];
 | 
			
		||||
    int currentMessageLen = 0;
 | 
			
		||||
 | 
			
		||||
    MemoryBlock pendingSysexData;
 | 
			
		||||
    double pendingSysexTime = 0;
 | 
			
		||||
    int pendingSysexSize = 0;
 | 
			
		||||
 | 
			
		||||
    JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 STATICMETHOD (getMinBufferSize,            "getMinBufferSize",             "(III)I") \
 | 
			
		||||
 STATICMETHOD (getNativeOutputSampleRate,   "getNativeOutputSampleRate",    "(I)I") \
 | 
			
		||||
 METHOD (constructor,   "<init>",   "(IIIIII)V") \
 | 
			
		||||
@ -34,11 +34,11 @@ namespace juce
 | 
			
		||||
 METHOD (flush,         "flush",    "()V") \
 | 
			
		||||
 METHOD (write,         "write",    "([SII)I") \
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack");
 | 
			
		||||
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
 | 
			
		||||
 METHOD (constructor,       "<init>",           "(IIIII)V") \
 | 
			
		||||
 METHOD (getState,          "getState",         "()I") \
 | 
			
		||||
@ -47,14 +47,7 @@ DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack");
 | 
			
		||||
 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");
 | 
			
		||||
DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
@ -198,11 +191,11 @@ public:
 | 
			
		||||
        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));
 | 
			
		||||
            outputDevice = GlobalRef (LocalRef<jobject>(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);
 | 
			
		||||
            const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24);
 | 
			
		||||
            getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0;
 | 
			
		||||
 | 
			
		||||
            int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState);
 | 
			
		||||
@ -232,11 +225,11 @@ public:
 | 
			
		||||
            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)))));
 | 
			
		||||
                inputDevice = GlobalRef (LocalRef<jobject>(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)
 | 
			
		||||
 | 
			
		||||
@ -23,24 +23,346 @@
 | 
			
		||||
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")
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This byte-code is generated from native/java/com/roli/juce/JuceMidiSupport.java with min sdk version 23
 | 
			
		||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
 | 
			
		||||
static const uint8 javaMidiByteCode[] =
 | 
			
		||||
{31,139,8,8,173,175,226,91,0,3,106,117,99,101,95,97,117,100,105,111,95,100,101,118,105,99,101,115,46,100,101,120,0,149,
 | 
			
		||||
124,11,124,220,69,181,255,153,223,99,119,179,217,164,155,77,218,164,105,178,217,164,73,179,133,60,155,182,164,77,26,154,
 | 
			
		||||
164,105,155,118,251,160,217,22,105,144,186,77,182,237,150,100,55,236,110,250,0,175,148,135,180,32,42,42,143,42,92,254,40,
 | 
			
		||||
175,130,168,232,5,46,34,42,8,87,20,69,184,92,212,122,125,1,194,223,130,200,67,65,68,196,222,239,153,153,125,164,45,84,
 | 
			
		||||
203,231,187,103,126,103,206,156,153,57,115,230,204,153,223,46,25,141,238,113,183,117,44,160,7,183,254,71,91,73,96,244,
 | 
			
		||||
225,231,218,251,238,107,108,49,191,55,239,138,244,25,191,14,80,247,85,235,137,38,136,104,207,166,249,62,210,255,30,90,71,
 | 
			
		||||
116,150,80,252,1,224,25,155,104,19,232,17,7,81,0,244,117,55,209,93,76,11,137,10,64,215,151,128,223,3,121,104,216,209,76,
 | 
			
		||||
180,19,24,7,146,192,36,112,61,112,35,112,8,184,27,184,7,120,0,120,8,120,26,40,110,33,90,7,156,11,196,129,36,176,27,56,8,
 | 
			
		||||
124,23,248,30,240,3,224,151,192,107,192,123,64,105,43,209,12,160,18,168,1,234,129,83,128,22,160,3,88,4,108,1,46,5,110,7,
 | 
			
		||||
158,6,204,54,162,54,96,24,216,7,124,25,248,9,240,6,224,111,39,234,7,206,1,46,4,190,2,60,13,188,3,84,207,35,90,11,236,7,
 | 
			
		||||
238,0,126,2,188,1,56,58,136,170,128,5,192,32,240,97,32,9,252,16,120,23,104,153,79,180,21,184,10,248,13,80,191,128,232,76,
 | 
			
		||||
224,227,192,109,192,227,192,203,64,209,66,162,38,96,37,176,5,72,2,251,129,235,129,175,1,223,7,94,0,204,211,136,102,2,109,
 | 
			
		||||
192,48,144,0,46,1,14,2,119,1,15,3,63,7,142,0,127,3,90,59,137,150,2,67,192,78,224,223,128,135,128,103,128,231,129,130,69,
 | 
			
		||||
68,30,192,11,76,7,102,1,179,129,70,160,9,152,7,44,2,122,128,126,96,16,88,7,108,2,62,12,68,128,81,96,7,48,6,76,0,87,3,143,
 | 
			
		||||
0,47,1,255,0,74,23,195,55,128,38,96,1,176,24,232,7,206,0,206,1,118,2,73,224,82,224,38,224,30,224,251,192,175,128,87,129,
 | 
			
		||||
119,0,179,11,227,3,170,129,122,96,30,176,24,88,5,156,1,124,8,136,0,113,96,15,240,113,224,83,192,13,192,205,192,109,192,
 | 
			
		||||
55,128,7,128,71,129,159,0,191,6,94,6,222,1,236,110,216,0,168,2,234,129,86,96,49,176,19,72,3,215,1,247,3,223,5,158,0,126,
 | 
			
		||||
1,252,22,120,25,120,13,120,23,40,95,66,52,31,88,13,124,4,56,31,248,12,112,61,240,85,224,155,192,15,129,159,2,191,1,10,
 | 
			
		||||
176,95,188,64,121,143,218,59,61,192,89,192,94,224,11,192,189,192,19,192,207,128,183,0,227,116,162,18,224,20,96,62,208,15,
 | 
			
		||||
132,129,109,64,2,248,24,176,31,248,12,112,29,240,37,224,86,224,78,224,235,192,127,2,223,1,126,8,252,12,248,29,240,10,240,
 | 
			
		||||
22,96,44,133,31,0,51,128,90,160,21,152,15,116,3,43,129,51,128,51,129,17,96,23,112,25,240,57,224,58,224,6,224,139,192,237,
 | 
			
		||||
192,87,129,251,129,239,0,143,0,63,2,126,11,252,21,40,238,37,106,0,122,128,245,192,8,144,4,46,6,62,15,220,9,60,12,60,14,
 | 
			
		||||
28,6,94,6,172,62,34,31,16,0,130,64,15,48,4,108,1,226,192,167,128,175,1,143,3,255,11,188,8,188,6,252,13,16,253,216,87,64,
 | 
			
		||||
25,48,23,24,0,206,1,70,129,113,96,15,112,49,112,37,112,29,240,69,224,110,224,1,224,113,224,73,224,247,192,107,192,187,
 | 
			
		||||
128,107,25,246,6,112,42,48,31,232,6,250,128,21,192,102,32,1,236,1,46,6,174,0,174,2,238,2,238,3,30,3,158,1,94,0,222,0,222,
 | 
			
		||||
6,254,1,20,32,184,86,0,1,96,14,208,6,116,1,189,192,32,16,6,62,12,164,129,79,3,119,0,119,3,211,16,115,203,128,58,96,54,80,
 | 
			
		||||
15,52,0,115,128,70,32,8,204,5,78,1,78,5,154,0,132,89,66,216,36,132,65,66,184,35,132,53,66,8,35,132,41,66,104,34,132,33,
 | 
			
		||||
66,136,33,132,13,66,104,32,108,93,194,214,35,108,15,130,123,19,92,150,224,42,132,101,33,152,151,96,18,90,166,207,135,229,
 | 
			
		||||
192,10,96,37,48,8,172,2,86,3,33,96,13,176,22,88,199,231,4,112,6,176,1,24,2,194,164,206,149,15,1,155,129,179,129,115,128,
 | 
			
		||||
45,64,4,24,1,70,129,40,112,46,112,1,240,49,224,66,96,31,112,17,112,49,112,9,41,155,100,254,121,53,125,22,147,47,209,229,
 | 
			
		||||
35,40,87,130,26,250,153,203,166,46,215,232,242,179,90,198,210,252,90,93,126,93,243,93,121,242,56,2,233,239,154,95,168,
 | 
			
		||||
249,179,128,34,110,211,164,248,197,121,125,77,203,43,251,242,228,203,180,60,151,43,242,218,86,230,245,85,165,199,198,50,
 | 
			
		||||
126,45,83,163,203,204,175,209,114,30,173,167,78,203,84,235,114,89,147,146,229,114,149,110,91,159,215,182,65,183,229,126,
 | 
			
		||||
216,135,130,122,12,45,121,227,108,205,27,91,91,222,216,184,220,214,164,242,2,46,119,54,229,248,25,123,182,231,233,105,
 | 
			
		||||
207,27,63,151,151,230,149,51,242,157,121,250,217,15,87,234,126,23,107,62,251,194,18,93,30,211,101,214,57,174,203,235,81,
 | 
			
		||||
142,235,242,135,80,78,232,242,104,147,202,105,184,156,70,121,183,46,127,20,229,243,116,249,0,202,73,93,190,10,229,73,93,
 | 
			
		||||
190,1,229,93,186,124,75,94,249,238,60,157,15,230,149,61,121,229,71,243,202,63,206,235,247,153,60,254,179,121,229,35,121,
 | 
			
		||||
253,190,158,199,255,107,94,91,222,208,123,50,125,53,231,228,43,80,222,171,203,129,230,92,219,165,121,122,218,242,244,119,
 | 
			
		||||
230,143,225,212,92,185,169,57,215,215,124,148,211,25,61,40,159,175,203,43,155,115,182,90,143,114,74,151,207,110,86,123,
 | 
			
		||||
181,71,175,209,71,117,153,215,232,223,116,57,157,87,110,203,43,103,124,160,87,183,229,114,95,158,63,244,231,249,195,50,
 | 
			
		||||
205,159,165,203,150,244,243,54,186,143,20,93,42,184,205,52,250,132,108,219,78,159,146,244,52,250,140,164,46,234,17,236,
 | 
			
		||||
183,21,116,5,219,10,189,191,36,169,160,87,37,109,160,217,178,126,14,53,11,142,5,101,82,174,86,243,107,53,127,182,126,102,
 | 
			
		||||
122,134,224,125,101,209,181,196,212,75,127,145,84,213,215,235,250,6,61,158,6,68,219,107,36,237,163,59,37,45,167,55,37,
 | 
			
		||||
157,79,239,232,122,191,80,52,32,212,190,60,68,76,123,232,15,164,158,231,10,142,247,53,116,21,49,109,160,183,137,227,155,
 | 
			
		||||
139,190,39,169,73,143,74,106,211,255,18,199,55,39,221,40,105,29,61,168,233,255,176,205,112,82,220,160,233,87,37,181,232,
 | 
			
		||||
191,36,93,67,11,161,223,6,223,73,28,251,86,208,42,193,116,1,173,21,156,247,43,190,59,75,221,116,157,164,5,180,28,245,30,
 | 
			
		||||
173,167,72,215,23,129,115,157,164,133,180,76,40,58,32,56,46,22,209,119,137,105,45,253,146,56,118,171,241,120,17,61,127,
 | 
			
		||||
44,233,52,154,41,152,122,169,90,112,60,87,227,230,184,254,51,77,127,69,42,166,254,72,210,51,232,176,164,37,244,11,205,
 | 
			
		||||
231,250,50,173,183,12,167,83,47,244,76,215,227,42,199,105,244,152,164,173,84,46,152,46,162,10,73,187,105,158,164,93,180,
 | 
			
		||||
73,112,108,86,237,43,224,225,255,174,41,219,107,166,214,83,137,241,127,139,56,134,250,232,126,226,216,107,208,45,210,15,
 | 
			
		||||
151,201,250,26,172,163,162,130,30,150,180,129,126,40,105,152,254,91,210,229,100,74,127,61,133,74,37,61,149,202,36,93,79,
 | 
			
		||||
115,36,29,164,213,146,14,208,70,233,151,167,75,125,1,61,46,166,255,33,105,51,253,90,210,16,189,162,249,211,164,252,106,
 | 
			
		||||
154,46,233,42,234,23,138,63,168,233,26,233,207,61,82,95,173,214,87,171,245,213,106,61,181,186,93,173,110,87,171,219,213,
 | 
			
		||||
105,249,58,45,87,167,229,234,180,92,157,150,155,77,75,165,254,217,200,58,44,249,220,65,182,166,14,73,219,201,41,233,124,
 | 
			
		||||
114,105,90,160,249,94,77,75,36,109,35,159,166,51,228,190,234,149,122,235,209,255,231,37,173,163,135,36,117,208,15,72,157,
 | 
			
		||||
115,143,75,58,151,186,244,126,114,202,253,165,230,215,0,143,184,71,210,153,116,175,164,106,125,26,224,23,223,151,116,54,
 | 
			
		||||
61,33,233,70,250,137,164,97,122,82,211,167,36,45,165,167,181,220,51,146,214,211,79,37,157,69,63,151,180,147,220,178,223,
 | 
			
		||||
211,168,80,83,143,80,252,34,73,23,83,177,80,251,191,82,210,25,52,75,210,10,170,146,116,45,213,75,218,66,13,66,197,139,
 | 
			
		||||
249,146,14,80,88,198,133,38,57,159,57,200,176,190,166,227,194,111,101,60,152,11,11,40,234,148,116,58,125,91,210,74,250,
 | 
			
		||||
14,241,121,126,138,228,183,106,249,86,120,236,135,4,159,219,74,190,77,219,167,13,30,253,8,241,249,172,244,183,195,206,47,
 | 
			
		||||
19,231,141,253,82,174,3,30,206,126,63,95,183,155,15,185,253,250,249,122,253,252,255,36,157,67,127,212,207,29,66,229,156,
 | 
			
		||||
43,37,29,162,33,193,249,103,35,93,73,156,131,42,61,11,117,251,133,144,191,73,210,26,217,207,66,100,183,127,146,52,64,237,
 | 
			
		||||
66,241,89,223,105,186,221,105,186,255,211,116,63,167,233,126,78,211,253,116,98,252,191,33,166,200,136,4,231,23,106,92,
 | 
			
		||||
139,53,237,210,122,186,144,205,158,46,56,247,85,207,221,218,191,248,12,2,91,190,247,32,185,255,113,94,115,162,141,36,247,
 | 
			
		||||
189,53,42,199,18,142,92,142,196,245,46,156,105,11,215,170,231,128,110,207,252,167,78,85,180,2,116,173,174,175,213,245,
 | 
			
		||||
109,121,245,109,160,151,233,250,217,90,175,157,167,127,37,234,191,170,235,235,53,191,59,175,254,67,168,127,81,215,55,104,
 | 
			
		||||
253,211,129,195,90,255,14,80,207,58,85,63,71,183,203,31,255,221,168,63,160,235,27,243,198,151,169,127,8,245,55,233,122,
 | 
			
		||||
206,175,127,129,196,255,217,144,146,251,131,166,127,11,229,234,10,215,228,202,229,107,84,125,93,30,239,84,93,94,8,186,36,
 | 
			
		||||
175,188,114,141,202,211,89,102,8,229,115,116,219,152,166,231,107,250,9,77,111,210,244,94,77,127,156,215,199,111,53,239,
 | 
			
		||||
101,169,211,144,229,111,14,168,187,195,132,183,8,207,117,240,153,9,47,251,238,176,215,66,84,31,246,26,52,236,51,112,46,
 | 
			
		||||
177,60,235,121,124,64,229,254,97,212,156,231,189,148,248,212,139,7,118,96,141,221,50,223,183,180,220,127,15,168,123,193,
 | 
			
		||||
121,178,23,143,136,7,12,236,35,200,122,109,249,204,113,205,68,29,203,254,102,64,157,105,225,128,69,225,90,11,50,95,66,
 | 
			
		||||
141,91,204,46,89,6,221,55,99,124,30,248,224,50,41,99,203,83,30,119,121,180,153,1,154,244,222,134,62,61,34,233,189,133,
 | 
			
		||||
219,24,157,70,17,120,183,162,204,109,60,228,243,197,219,22,193,131,130,175,23,235,145,17,189,51,160,236,192,119,21,135,
 | 
			
		||||
156,25,158,151,171,187,160,175,100,94,153,77,190,218,142,178,18,140,163,4,253,121,176,111,10,41,220,206,227,226,155,145,
 | 
			
		||||
199,136,7,110,130,207,250,122,59,202,106,16,191,166,83,165,177,147,206,11,180,131,151,107,225,59,166,197,23,101,173,165,
 | 
			
		||||
109,209,141,8,90,44,231,194,125,7,150,171,187,74,190,173,122,161,101,17,250,85,250,191,161,245,251,196,52,17,110,87,150,
 | 
			
		||||
23,82,242,124,105,169,224,91,110,104,98,237,109,208,117,38,207,163,220,231,208,250,160,199,77,149,22,244,216,69,82,79,24,
 | 
			
		||||
125,199,229,133,209,35,22,137,76,157,71,215,5,255,212,89,176,128,234,12,55,60,129,109,86,105,89,232,175,141,173,108,197,
 | 
			
		||||
3,94,196,226,58,179,8,117,62,88,46,30,40,67,6,204,252,233,184,181,122,44,95,3,151,194,52,59,189,20,61,76,67,107,143,189,
 | 
			
		||||
198,182,28,231,121,175,83,237,189,165,104,229,177,227,75,11,169,247,227,193,111,199,3,30,220,104,131,223,164,172,127,141,
 | 
			
		||||
46,87,247,204,169,254,245,111,240,175,98,228,97,14,57,163,241,229,234,110,57,225,109,65,155,225,217,5,52,92,239,160,225,
 | 
			
		||||
6,55,109,158,227,130,229,207,14,56,229,218,218,210,191,4,93,180,92,197,16,159,25,238,117,80,167,112,18,211,184,247,84,
 | 
			
		||||
212,133,123,11,192,41,144,52,220,231,70,95,31,133,157,135,251,161,179,223,1,45,69,122,5,124,122,5,130,71,84,28,98,221,66,
 | 
			
		||||
52,227,248,18,114,76,215,162,15,142,153,113,126,193,69,9,239,229,218,191,212,46,35,186,113,185,218,135,62,88,69,104,222,
 | 
			
		||||
45,203,115,126,88,140,185,241,93,251,206,229,106,223,44,41,244,208,208,69,46,114,238,115,126,78,220,34,238,181,190,191,
 | 
			
		||||
203,181,84,203,90,250,182,254,64,94,123,67,239,165,239,47,87,113,46,236,45,80,94,232,197,140,33,177,209,235,148,126,192,
 | 
			
		||||
207,241,64,19,246,148,207,123,182,215,57,165,237,147,31,208,182,51,219,182,153,219,82,166,45,143,133,199,112,88,175,219,
 | 
			
		||||
132,247,0,71,15,225,161,97,163,144,134,225,41,197,217,117,56,146,183,14,133,122,29,10,97,177,217,114,29,60,122,29,60,88,
 | 
			
		||||
135,162,236,58,64,79,127,225,191,176,14,239,101,215,97,229,9,215,193,94,161,215,1,30,228,212,163,47,4,175,148,231,221,
 | 
			
		||||
174,71,5,26,95,138,44,43,154,235,183,79,232,126,223,86,239,71,116,191,238,204,90,214,173,200,173,69,134,23,204,227,153,
 | 
			
		||||
50,218,17,181,172,80,239,84,134,197,52,216,138,45,54,108,20,203,248,170,222,204,44,206,107,147,89,231,101,39,224,173,95,
 | 
			
		||||
145,31,195,44,57,167,179,87,168,56,234,11,116,216,211,176,190,241,64,1,180,134,3,188,219,93,188,239,16,47,46,144,183,140,
 | 
			
		||||
156,158,115,79,160,123,247,9,120,151,156,128,247,233,99,230,199,255,174,63,1,239,214,60,158,45,45,71,244,181,21,188,179,
 | 
			
		||||
217,14,165,176,195,157,210,14,56,111,204,18,26,182,120,132,150,180,162,73,15,173,80,239,100,170,140,122,170,54,124,98,
 | 
			
		||||
184,221,135,85,253,50,106,224,143,237,94,172,215,52,73,227,240,71,161,75,28,37,88,210,139,231,18,72,184,37,141,123,43,52,
 | 
			
		||||
191,132,252,6,238,96,194,111,52,138,34,17,124,155,103,51,19,117,53,114,124,166,204,23,28,210,103,231,94,216,220,52,87,
 | 
			
		||||
151,13,250,245,10,149,59,86,153,24,139,25,222,0,221,198,108,98,26,247,206,228,88,39,226,222,58,121,10,249,230,119,244,
 | 
			
		||||
205,0,183,150,163,180,81,105,125,22,254,220,138,200,201,103,146,137,189,228,202,158,12,126,179,4,168,196,242,5,255,94,
 | 
			
		||||
132,82,163,161,206,251,57,104,217,172,125,73,32,223,205,248,239,187,43,84,125,216,235,149,107,109,104,111,19,43,51,231,
 | 
			
		||||
178,26,141,140,155,94,245,214,78,157,203,106,15,58,87,242,253,14,115,16,152,131,8,183,249,56,39,167,112,27,230,130,252,
 | 
			
		||||
142,159,195,243,160,33,144,70,196,245,11,246,127,191,104,36,213,103,169,236,171,12,251,70,157,193,190,149,234,125,166,
 | 
			
		||||
207,154,240,54,192,114,195,225,50,26,222,84,6,139,148,81,165,249,22,180,148,227,230,227,49,106,140,114,26,222,80,14,62,
 | 
			
		||||
110,154,56,87,42,13,236,40,115,187,220,201,243,176,214,117,198,66,248,192,65,142,229,27,102,226,105,62,158,62,45,159,42,
 | 
			
		||||
166,212,85,78,121,154,46,245,197,189,115,216,242,84,107,250,140,5,243,220,124,15,199,105,155,68,28,59,108,24,88,91,41,
 | 
			
		||||
211,54,151,54,88,193,31,187,244,25,115,250,74,149,43,134,71,42,208,254,115,188,51,44,206,31,44,114,155,157,102,155,204,
 | 
			
		||||
31,44,57,110,63,133,17,176,106,76,175,180,169,153,91,97,211,183,160,99,232,247,71,245,10,155,149,182,90,225,161,236,10,
 | 
			
		||||
255,226,168,94,97,51,30,248,4,206,72,214,252,228,209,18,195,103,4,255,225,200,156,117,43,213,187,234,112,95,37,244,223,
 | 
			
		||||
192,243,48,146,222,219,53,189,5,148,91,205,146,227,49,164,230,86,10,247,67,54,112,189,28,75,13,214,48,238,253,136,28,1,
 | 
			
		||||
247,50,36,229,95,56,90,34,124,34,248,15,142,51,202,123,46,92,169,222,123,87,217,13,84,109,135,211,60,235,107,120,182,214,
 | 
			
		||||
236,190,62,142,243,105,101,7,204,217,33,100,38,229,64,93,167,53,67,246,236,128,246,26,211,79,135,17,239,194,184,194,212,
 | 
			
		||||
88,202,26,124,158,175,177,12,33,68,240,247,126,187,196,40,178,252,118,163,197,126,209,34,123,109,37,151,244,19,131,238,
 | 
			
		||||
91,169,238,62,170,255,9,111,140,117,90,195,163,51,200,111,215,153,106,181,77,140,35,222,118,30,237,194,220,59,141,82,204,
 | 
			
		||||
113,2,123,128,51,139,171,112,235,58,12,7,207,212,6,223,80,61,241,28,76,158,67,47,103,137,215,194,59,61,86,141,133,44,81,
 | 
			
		||||
142,129,163,65,67,118,255,240,205,88,237,148,63,106,155,87,25,24,139,17,238,85,51,103,11,242,188,161,79,116,138,10,57,
 | 
			
		||||
111,83,90,220,47,111,156,53,66,205,89,230,92,222,26,153,115,117,156,113,228,168,223,224,88,226,163,224,123,42,154,168,
 | 
			
		||||
125,210,40,123,10,18,231,130,220,175,99,16,209,141,251,116,163,79,119,167,183,140,124,115,121,54,55,20,121,176,94,107,41,
 | 
			
		||||
252,240,76,204,224,11,200,143,185,119,39,44,225,119,151,144,111,102,240,213,13,237,179,104,34,16,199,125,212,227,236,116,
 | 
			
		||||
46,162,240,46,140,197,129,168,231,232,128,84,39,124,113,67,123,53,218,206,66,46,204,118,43,64,6,31,32,231,195,214,11,187,
 | 
			
		||||
28,242,77,39,191,107,129,246,58,171,3,122,62,137,53,139,183,253,59,181,91,126,119,240,41,140,216,221,40,84,251,74,110,
 | 
			
		||||
239,234,116,189,116,180,14,103,220,196,210,21,244,80,103,240,121,191,27,51,123,144,228,157,186,31,51,226,239,56,84,246,
 | 
			
		||||
178,66,250,22,123,241,126,204,141,223,253,249,28,225,20,118,90,224,20,248,122,149,133,121,90,225,212,116,216,236,243,124,
 | 
			
		||||
2,165,148,127,155,210,202,183,203,12,144,173,109,75,127,45,151,214,182,165,63,183,42,89,248,119,169,244,58,246,239,45,
 | 
			
		||||
144,15,190,84,100,250,173,70,211,103,14,163,222,15,221,51,143,213,152,183,131,43,243,118,240,28,146,178,208,56,91,106,
 | 
			
		||||
236,64,187,79,201,118,53,102,29,228,214,178,246,223,13,239,154,9,185,19,69,131,186,140,46,229,11,166,160,78,68,3,166,53,
 | 
			
		||||
166,37,79,29,51,239,201,33,159,50,17,163,130,117,255,82,233,253,12,202,156,141,112,214,196,247,232,185,176,223,124,82,
 | 
			
		||||
119,32,129,155,165,186,11,24,244,237,65,229,159,190,64,149,128,21,225,109,87,242,72,140,69,70,0,43,184,13,235,192,235,
 | 
			
		||||
140,27,138,215,207,39,223,188,233,210,115,250,228,205,199,137,88,29,252,139,138,216,19,129,237,90,214,98,238,31,252,194,
 | 
			
		||||
39,115,38,142,73,179,208,99,128,248,62,207,125,87,203,236,129,253,246,233,65,149,87,251,106,217,59,125,38,223,99,156,114,
 | 
			
		||||
39,26,242,198,198,239,106,130,239,229,242,197,231,7,149,111,248,2,19,129,115,229,141,171,36,91,119,36,83,231,205,213,101,
 | 
			
		||||
250,121,53,211,79,201,7,247,227,211,54,249,7,228,183,18,199,179,18,218,232,170,162,78,215,90,220,111,212,110,114,33,159,
 | 
			
		||||
9,23,206,160,218,7,125,46,113,249,130,31,174,194,9,80,88,224,51,244,46,117,113,155,13,69,179,168,227,240,34,172,2,191,
 | 
			
		||||
233,246,20,213,254,198,231,90,112,164,157,150,91,69,174,184,215,207,246,117,116,236,154,131,250,106,185,234,178,205,180,
 | 
			
		||||
106,234,248,115,21,120,85,188,250,54,175,3,252,21,123,164,56,115,174,187,42,11,222,147,81,255,32,234,107,236,118,222,181,
 | 
			
		||||
182,175,49,120,207,97,151,75,4,159,58,236,42,16,226,242,224,127,250,221,149,72,84,131,127,46,114,97,191,185,56,227,219,
 | 
			
		||||
128,214,31,201,198,166,45,176,133,41,215,37,182,74,125,247,232,43,245,89,62,17,126,12,179,218,35,150,47,184,108,30,45,39,
 | 
			
		||||
167,131,71,143,243,164,160,246,11,98,96,193,173,245,180,220,40,176,121,244,56,37,10,59,126,57,147,58,158,155,78,117,246,
 | 
			
		||||
156,204,169,239,232,88,82,40,103,132,122,107,98,233,199,40,218,239,28,42,49,69,127,240,47,135,17,179,15,219,182,8,254,
 | 
			
		||||
250,176,141,128,191,41,248,164,207,12,254,89,221,221,121,28,95,90,165,214,135,207,58,206,179,124,237,29,94,19,126,27,198,
 | 
			
		||||
121,238,19,241,165,65,106,243,5,223,225,247,202,134,204,151,238,130,252,77,28,223,138,176,34,69,105,49,147,119,175,139,
 | 
			
		||||
199,225,194,56,124,78,95,1,223,202,194,158,74,220,232,246,115,236,47,102,159,125,133,220,158,69,158,115,100,47,144,243,
 | 
			
		||||
248,122,58,94,105,67,212,99,235,186,200,227,169,44,86,103,234,43,210,186,56,83,109,117,131,70,164,176,148,62,229,251,33,
 | 
			
		||||
180,237,244,116,83,142,119,61,120,30,103,141,211,202,227,221,8,94,93,97,189,228,184,176,23,92,56,99,38,206,60,135,10,107,
 | 
			
		||||
143,27,27,34,228,43,133,117,197,93,56,151,14,97,214,157,5,109,84,234,121,202,121,209,67,182,151,243,67,236,236,165,119,
 | 
			
		||||
208,119,188,254,162,226,204,120,60,172,131,119,231,67,228,113,119,186,225,145,119,33,211,122,168,6,214,116,34,110,187,
 | 
			
		||||
100,156,112,202,248,224,164,52,206,151,82,242,23,5,159,43,242,248,139,26,61,165,158,81,10,62,93,228,9,190,205,123,99,18,
 | 
			
		||||
30,193,223,101,85,235,239,84,84,222,118,80,92,216,124,163,56,40,56,199,51,100,94,221,182,90,125,231,85,229,132,205,157,
 | 
			
		||||
124,54,185,113,122,179,205,113,118,27,225,107,213,124,12,94,7,216,232,50,172,67,167,3,209,245,90,196,162,192,103,233,54,
 | 
			
		||||
60,47,114,52,208,84,185,235,33,231,113,212,56,56,234,70,228,185,63,181,254,70,212,179,134,58,151,159,38,218,230,209,33,
 | 
			
		||||
147,79,231,203,200,239,44,38,61,2,139,87,143,243,136,74,151,90,189,203,100,92,199,234,137,42,105,45,33,243,82,73,29,108,
 | 
			
		||||
177,24,86,183,211,134,111,13,169,24,218,105,186,116,84,85,209,148,163,168,155,130,79,22,57,130,79,20,57,252,206,70,135,
 | 
			
		||||
58,99,199,97,247,9,29,63,119,203,189,132,8,122,97,115,122,23,114,132,66,25,53,4,237,131,141,62,204,54,194,76,171,165,109,
 | 
			
		||||
220,228,182,249,60,61,75,190,163,89,147,141,213,97,129,222,133,69,190,50,223,244,14,36,57,62,43,124,141,58,97,108,121,86,
 | 
			
		||||
221,174,169,60,179,176,162,175,30,213,103,150,60,97,54,52,206,34,173,221,213,177,255,213,163,178,45,172,217,32,61,87,157,
 | 
			
		||||
56,182,44,171,19,7,209,187,44,248,227,78,225,210,183,22,117,99,9,95,195,107,115,53,106,253,14,228,221,182,223,209,104,
 | 
			
		||||
171,185,158,37,227,196,230,236,251,171,71,86,79,189,251,241,61,248,71,171,179,241,118,253,94,154,23,246,200,236,195,144,
 | 
			
		||||
117,63,93,173,238,156,252,190,204,103,76,108,216,75,253,94,174,47,144,251,29,247,154,213,234,183,12,136,59,6,175,141,75,
 | 
			
		||||
174,17,191,153,230,44,208,164,105,134,58,209,249,61,159,19,22,236,180,113,210,88,193,183,112,95,177,26,141,112,114,58,
 | 
			
		||||
233,86,182,207,193,187,103,131,35,156,44,3,111,134,204,100,125,211,235,28,179,225,47,67,180,203,25,95,138,19,49,234,65,
 | 
			
		||||
150,131,21,207,107,39,91,9,191,37,230,5,31,231,59,234,126,10,254,77,189,151,116,99,166,252,93,231,169,210,6,21,217,24,
 | 
			
		||||
229,8,169,123,126,38,38,53,34,38,169,59,167,178,210,180,144,178,71,216,59,83,238,126,126,127,164,252,194,162,138,144,250,
 | 
			
		||||
189,5,207,209,45,119,6,71,55,181,147,194,215,170,168,114,155,228,227,206,114,173,138,40,183,201,181,134,127,26,106,213,
 | 
			
		||||
12,185,106,134,174,191,17,245,124,226,222,33,189,25,153,172,119,43,123,138,204,94,236,236,46,163,236,46,98,207,135,44,
 | 
			
		||||
172,11,223,235,159,114,195,112,196,3,147,216,21,42,123,240,59,131,247,40,175,47,18,149,14,220,154,92,165,242,214,116,25,
 | 
			
		||||
113,188,230,179,100,25,172,178,86,223,181,207,208,241,226,76,157,255,26,52,116,97,243,166,161,236,59,154,177,80,254,59,
 | 
			
		||||
154,179,196,44,58,219,168,162,179,204,106,249,246,73,221,48,63,26,82,239,203,125,98,17,98,238,52,228,31,87,200,76,138,41,
 | 
			
		||||
124,216,236,152,247,246,81,231,250,26,228,230,27,250,170,105,3,218,118,204,123,245,232,198,190,42,218,104,86,161,124,228,
 | 
			
		||||
232,134,190,89,224,227,196,157,247,252,81,95,73,240,183,153,247,112,68,159,12,169,239,10,234,112,55,216,208,59,139,158,
 | 
			
		||||
246,237,3,173,166,82,115,31,45,104,47,149,229,187,107,39,2,31,231,24,235,189,140,87,223,216,216,139,243,30,59,197,247,
 | 
			
		||||
198,55,106,167,137,82,113,33,5,95,131,214,191,169,219,48,209,23,245,58,251,96,179,26,192,143,185,100,222,5,29,10,169,124,
 | 
			
		||||
71,205,183,88,199,82,131,190,18,210,239,161,68,238,13,169,73,37,66,191,19,69,206,244,238,209,42,163,17,247,131,173,194,
 | 
			
		||||
79,139,4,178,106,81,131,53,235,64,134,30,3,199,47,249,193,35,153,156,191,68,234,16,250,123,12,33,179,43,83,247,245,88,72,
 | 
			
		||||
125,127,82,73,234,62,108,200,222,44,249,93,109,21,242,180,106,177,21,245,139,136,51,247,6,244,49,138,182,60,19,191,228,7,
 | 
			
		||||
95,205,220,191,139,116,31,213,217,62,170,228,111,79,229,221,95,219,130,231,250,81,159,250,254,231,82,208,43,179,191,110,
 | 
			
		||||
85,255,14,234,231,140,60,255,182,233,22,240,238,58,134,63,168,249,247,31,211,254,209,99,158,159,242,169,62,51,191,41,226,
 | 
			
		||||
119,135,207,250,212,111,122,94,247,169,239,73,254,234,83,191,91,56,226,83,191,105,98,189,219,88,184,84,253,94,5,14,77,
 | 
			
		||||
222,210,169,122,171,142,121,150,223,149,104,58,67,83,254,237,140,33,105,187,236,127,26,245,200,88,199,117,85,121,115,17,
 | 
			
		||||
164,114,25,67,63,153,154,246,80,238,119,81,153,239,118,12,73,219,228,243,28,205,239,207,202,121,116,91,245,11,15,213,215,
 | 
			
		||||
233,217,246,66,219,65,97,134,140,89,220,46,99,159,204,239,171,20,207,161,121,14,201,83,101,103,86,87,129,166,94,77,125,
 | 
			
		||||
90,198,167,245,10,101,62,202,124,167,101,72,58,139,50,223,177,177,44,127,199,59,71,247,203,223,171,206,209,123,176,1,255,
 | 
			
		||||
89,154,122,117,92,88,164,219,44,210,119,19,150,235,214,123,168,71,215,157,174,199,111,101,203,114,214,65,50,131,131,184,
 | 
			
		||||
203,204,165,250,182,142,190,206,182,129,5,189,205,3,203,6,58,155,231,247,117,116,52,247,158,182,160,189,121,97,255,64,
 | 
			
		||||
199,252,129,254,249,253,167,181,193,180,200,211,186,71,198,98,241,88,186,135,28,221,138,26,61,93,100,245,116,205,221,196,
 | 
			
		||||
159,40,123,251,198,38,163,233,68,34,189,99,77,36,30,217,30,77,210,226,99,57,129,104,50,153,72,46,14,140,36,38,199,70,3,
 | 
			
		||||
241,68,58,176,61,154,14,100,165,2,161,129,64,106,36,18,143,163,237,233,255,92,219,209,232,182,200,228,88,190,142,200,104,
 | 
			
		||||
100,34,13,5,149,203,38,199,199,247,102,249,43,34,233,116,127,100,108,108,107,100,4,23,155,65,50,6,67,100,14,134,66,84,51,
 | 
			
		||||
184,46,48,176,103,36,58,145,142,37,226,129,221,59,98,99,209,192,200,88,34,21,139,111,15,76,36,146,105,106,24,92,247,126,
 | 
			
		||||
245,227,177,209,24,134,176,43,54,18,37,177,138,172,85,27,251,7,168,100,213,228,72,116,13,106,6,227,19,147,233,245,172,
 | 
			
		||||
194,151,97,173,155,76,103,120,158,12,79,62,149,101,158,134,38,39,184,215,150,157,145,93,17,18,33,50,66,131,100,134,6,229,
 | 
			
		||||
7,122,192,7,50,10,12,219,12,225,195,10,133,54,135,168,62,20,137,143,38,19,177,209,214,173,153,217,182,102,231,221,171,
 | 
			
		||||
204,209,69,179,63,72,106,153,156,67,23,213,126,144,16,155,176,139,230,158,76,36,99,229,46,106,61,169,232,142,72,50,50,
 | 
			
		||||
130,225,197,82,233,216,72,23,157,122,178,6,203,162,169,145,100,108,34,157,72,158,120,32,99,209,156,124,40,58,164,124,233,
 | 
			
		||||
196,115,135,40,215,231,70,251,62,250,88,104,121,108,12,131,172,239,155,140,141,141,178,190,19,153,105,138,232,7,138,108,
 | 
			
		||||
136,166,224,178,39,158,173,22,25,138,166,211,112,176,84,174,203,15,152,66,70,184,139,102,102,133,70,18,241,116,52,158,
 | 
			
		||||
110,237,103,186,7,157,213,100,171,198,163,163,177,72,43,187,110,43,59,92,102,233,155,62,88,96,48,190,45,81,207,174,202,
 | 
			
		||||
133,252,225,188,175,116,23,53,124,176,208,80,58,146,158,196,168,235,222,79,44,187,129,242,93,233,24,25,29,29,234,149,202,
 | 
			
		||||
220,106,158,118,178,6,235,226,170,201,186,137,104,60,58,26,130,7,70,165,175,4,78,210,240,3,230,158,219,221,249,235,127,
 | 
			
		||||
140,208,134,232,72,52,182,139,245,148,102,69,18,169,214,190,201,248,232,24,150,161,44,159,185,50,194,76,136,150,231,115,
 | 
			
		||||
215,71,146,35,209,177,141,147,177,209,46,242,101,43,38,211,177,177,214,80,98,251,113,188,245,145,88,50,175,175,44,175,
 | 
			
		||||
139,54,30,207,236,62,137,155,156,52,62,224,32,104,11,141,36,198,91,147,137,177,88,235,78,68,181,214,99,66,91,253,177,145,
 | 
			
		||||
189,139,218,79,210,226,184,136,218,69,243,254,201,38,249,107,210,244,79,182,81,210,161,147,72,231,172,146,245,193,247,61,
 | 
			
		||||
113,186,104,217,191,172,45,199,97,23,13,71,82,231,158,220,80,199,105,57,249,164,51,19,94,31,73,239,224,48,241,129,210,
 | 
			
		||||
188,89,71,35,99,187,98,231,182,34,180,38,176,129,113,40,182,14,196,245,129,216,63,22,73,97,67,251,79,32,51,200,145,88,
 | 
			
		||||
215,215,158,160,126,77,116,124,171,22,136,66,164,250,4,34,67,177,237,113,68,140,36,118,73,229,9,170,195,59,146,137,221,
 | 
			
		||||
104,58,61,196,103,103,107,44,209,154,119,112,119,81,137,98,143,69,226,219,91,245,56,74,243,88,131,136,147,210,94,190,60,
 | 
			
		||||
230,186,173,59,163,35,233,169,188,161,116,18,51,205,118,35,121,178,235,200,86,222,191,85,121,236,100,116,91,235,153,209,
 | 
			
		||||
200,185,27,162,219,162,201,104,28,73,66,245,7,213,242,230,151,213,114,55,246,38,147,145,189,28,150,50,61,77,229,242,121,
 | 
			
		||||
117,2,118,247,241,35,237,201,142,63,39,154,154,202,91,25,73,97,51,78,100,12,146,207,59,94,16,199,205,113,130,224,77,29,
 | 
			
		||||
253,32,14,193,136,60,166,167,229,113,229,116,188,199,48,186,168,227,24,78,247,73,207,206,158,169,122,101,247,37,121,140,
 | 
			
		||||
112,108,156,215,114,250,177,44,181,139,74,142,219,38,212,123,28,235,196,249,102,222,65,16,72,237,197,153,49,30,72,69,147,
 | 
			
		||||
50,1,244,29,191,97,201,147,191,187,168,33,255,180,110,233,239,13,133,250,122,251,87,111,9,159,181,126,96,203,154,222,112,
 | 
			
		||||
255,202,45,161,117,67,97,18,155,200,216,132,132,111,19,82,84,107,211,224,230,65,114,108,90,133,20,112,21,216,72,252,54,
 | 
			
		||||
33,35,180,54,113,74,104,111,146,92,112,228,7,75,135,84,37,202,54,127,174,82,4,105,228,166,205,36,144,57,66,153,129,148,
 | 
			
		||||
209,24,238,163,186,225,147,103,49,205,195,255,82,86,80,255,79,136,99,219,13,159,96,139,77,97,102,246,88,97,100,100,36,
 | 
			
		||||
154,74,45,31,139,108,79,145,27,153,226,100,100,76,166,203,206,76,150,111,70,70,71,249,105,52,9,57,242,232,222,7,227,163,
 | 
			
		||||
209,61,104,173,158,100,11,119,100,98,66,39,67,228,136,164,148,39,110,61,38,75,166,202,44,39,52,32,195,158,90,219,141,27,
 | 
			
		||||
7,151,145,111,235,113,153,101,158,134,140,35,149,229,56,217,105,167,242,228,182,232,235,66,193,214,116,175,30,181,107,
 | 
			
		||||
107,90,201,65,76,151,82,124,22,195,4,228,216,154,230,115,132,236,173,156,8,146,103,68,31,40,225,189,19,81,114,96,20,200,
 | 
			
		||||
4,168,120,100,74,30,77,246,200,88,52,146,100,146,72,69,201,137,92,48,14,27,83,161,46,72,133,46,206,16,35,177,120,74,178,
 | 
			
		||||
101,105,117,116,175,20,150,54,242,232,66,56,177,17,58,108,236,130,120,154,196,40,185,71,179,41,56,57,244,92,92,138,194,
 | 
			
		||||
70,153,210,40,21,101,74,74,65,225,104,214,1,82,153,186,140,201,220,234,81,230,41,5,163,177,36,134,136,136,13,118,44,149,
 | 
			
		||||
25,186,35,122,30,150,62,69,5,114,83,246,39,70,97,192,104,38,182,83,203,182,8,110,101,163,129,116,34,48,146,140,70,210,
 | 
			
		||||
209,192,214,201,49,125,29,84,186,3,219,146,137,241,64,198,77,92,219,98,241,200,88,236,252,40,213,162,52,154,91,168,229,
 | 
			
		||||
137,100,222,197,73,9,215,176,72,102,67,159,72,192,222,22,75,194,153,60,219,96,162,209,204,130,187,185,67,229,198,100,109,
 | 
			
		||||
103,131,23,240,167,50,134,137,72,66,110,124,100,84,84,114,89,57,235,113,151,232,89,185,186,227,195,214,116,174,156,152,
 | 
			
		||||
24,139,141,200,51,48,227,224,165,96,31,55,206,138,124,102,126,6,46,181,28,127,109,34,23,216,242,164,164,18,148,150,169,
 | 
			
		||||
155,118,102,167,20,72,150,92,254,226,108,81,45,175,59,251,156,34,39,202,210,223,230,162,176,114,114,156,35,56,246,46,142,
 | 
			
		||||
74,101,156,19,26,20,162,240,37,73,70,165,6,214,75,85,178,160,147,185,181,145,113,94,45,78,79,212,102,159,137,90,126,58,
 | 
			
		||||
206,84,41,242,31,95,37,245,100,234,107,142,175,87,89,99,70,128,117,51,247,216,145,162,106,134,174,90,150,117,97,140,74,
 | 
			
		||||
143,154,199,72,213,40,228,114,208,227,134,93,136,234,76,5,21,101,30,38,57,23,162,114,253,200,103,199,148,70,46,93,145,82,
 | 
			
		||||
45,146,137,137,104,50,29,195,104,166,225,113,67,116,60,145,142,102,2,10,24,67,242,152,210,145,76,14,76,6,143,162,29,242,
 | 
			
		||||
114,161,175,35,228,220,17,73,173,101,223,113,161,176,67,238,48,107,71,2,126,93,192,159,202,111,69,140,204,216,232,30,178,
 | 
			
		||||
99,114,24,86,140,151,197,142,201,69,47,136,101,223,118,20,198,82,89,75,241,67,191,218,196,81,88,37,150,26,24,159,72,239,
 | 
			
		||||
229,130,92,2,174,206,189,38,113,197,116,214,64,46,78,62,87,202,238,119,102,167,237,217,153,255,198,196,60,23,225,202,129,
 | 
			
		||||
15,206,71,220,99,9,68,70,37,230,28,215,155,195,226,211,135,220,227,217,229,161,146,241,227,118,80,241,248,148,213,163,
 | 
			
		||||
194,241,60,207,49,198,199,201,28,79,109,199,71,122,146,172,56,47,146,205,159,136,33,241,232,110,222,62,48,83,156,205,102,
 | 
			
		||||
38,182,238,36,71,98,219,182,20,134,227,75,196,251,34,233,145,29,185,140,37,69,229,216,158,83,194,52,158,226,219,97,148,
 | 
			
		||||
178,99,43,120,135,208,244,99,185,103,38,97,29,169,69,153,19,219,93,246,175,212,144,55,17,207,189,28,145,26,74,242,57,170,
 | 
			
		||||
117,81,66,95,122,225,192,232,185,56,49,229,14,204,125,230,63,47,139,142,69,246,130,61,45,195,102,215,218,149,47,167,226,
 | 
			
		||||
71,102,34,206,68,124,249,216,100,106,7,121,18,241,53,233,201,12,27,35,227,241,40,191,220,144,74,197,168,130,57,99,49,142,
 | 
			
		||||
2,114,92,253,137,241,9,196,107,200,162,165,76,63,100,60,207,60,41,11,194,184,200,157,226,210,94,218,153,83,203,248,132,
 | 
			
		||||
192,93,26,178,165,216,4,241,99,194,27,185,153,169,203,69,92,206,249,154,159,31,167,220,41,207,140,165,119,228,246,88,77,
 | 
			
		||||
166,62,183,121,167,10,204,204,8,28,95,85,204,85,121,175,250,10,248,89,109,88,87,34,147,26,22,100,74,8,120,24,49,159,131,
 | 
			
		||||
137,92,19,59,177,155,67,112,233,4,124,242,216,89,85,158,128,57,148,142,78,132,119,39,168,124,74,93,46,50,145,53,193,25,
 | 
			
		||||
168,147,47,113,131,216,194,5,178,160,162,201,132,206,223,84,73,70,162,130,76,41,165,152,50,125,45,202,148,84,52,144,21,
 | 
			
		||||
50,148,20,103,74,225,196,114,132,6,50,121,103,207,72,70,183,243,75,149,228,212,55,51,228,72,74,47,34,183,162,42,98,168,
 | 
			
		||||
178,202,212,102,38,113,216,71,83,233,156,159,175,79,198,18,240,147,189,220,86,186,130,51,169,55,21,24,233,93,145,49,178,
 | 
			
		||||
146,236,87,102,114,50,78,37,169,108,254,170,95,158,81,105,42,47,239,206,48,157,153,55,205,174,212,200,142,232,40,18,6,
 | 
			
		||||
114,164,162,72,56,70,201,74,177,159,85,242,167,122,197,187,35,50,26,24,92,23,200,101,28,46,174,99,235,210,52,236,247,254,
 | 
			
		||||
252,164,172,16,12,246,218,53,28,66,139,249,65,231,144,147,177,81,84,238,224,235,4,246,13,38,106,165,56,5,177,83,242,161,
 | 
			
		||||
64,18,110,72,69,170,152,78,76,200,71,71,74,157,210,86,10,28,244,156,225,23,192,105,50,139,155,222,17,131,49,248,179,190,
 | 
			
		||||
13,21,184,234,160,209,248,4,57,211,9,121,223,163,233,147,241,19,185,210,204,99,216,121,14,83,49,25,127,159,21,180,97,241,
 | 
			
		||||
73,156,24,146,172,219,70,93,226,102,225,44,54,222,160,174,61,198,167,47,236,106,166,144,184,28,12,90,46,201,126,147,174,
 | 
			
		||||
17,22,255,191,111,197,116,171,16,100,89,223,52,22,143,56,139,143,154,116,191,81,180,217,38,186,92,136,175,179,252,39,133,
 | 
			
		||||
241,121,113,191,225,44,62,55,100,210,237,194,106,62,104,211,146,61,33,7,181,92,123,62,196,118,75,117,7,164,186,150,61,1,
 | 
			
		||||
58,67,252,192,112,54,65,244,83,194,108,49,252,187,141,109,213,33,83,92,37,10,90,174,104,217,108,26,223,54,10,175,219,108,
 | 
			
		||||
154,223,49,138,87,111,94,242,200,224,122,219,176,77,186,84,72,37,215,210,189,194,122,87,92,38,190,102,60,143,199,238,102,
 | 
			
		||||
252,235,166,223,10,114,86,135,182,172,222,219,220,108,236,170,246,155,244,13,209,66,15,129,89,220,221,77,143,49,165,183,
 | 
			
		||||
229,231,27,194,250,187,184,196,184,69,252,15,6,219,124,43,253,93,152,234,25,117,79,176,196,35,155,151,208,207,50,133,3,
 | 
			
		||||
134,169,186,82,29,209,125,198,9,186,249,140,161,186,57,200,148,14,25,89,133,107,182,24,23,84,215,40,161,27,101,229,205,
 | 
			
		||||
242,243,77,195,160,119,81,223,220,221,76,151,154,198,3,226,122,238,253,98,211,228,210,19,232,139,46,201,43,191,107,160,
 | 
			
		||||
252,132,177,15,229,37,171,175,163,143,115,213,109,170,234,178,188,242,229,92,126,79,149,15,112,249,91,134,44,239,231,14,
 | 
			
		||||
100,233,162,108,233,179,166,69,119,137,219,197,183,208,239,102,158,215,245,38,198,181,164,27,11,242,128,113,122,104,243,
 | 
			
		||||
112,207,218,115,122,154,109,50,246,118,57,136,30,144,149,161,152,41,254,191,40,221,251,136,92,196,230,115,108,178,197,
 | 
			
		||||
172,154,69,244,35,174,165,39,229,231,79,165,228,254,61,254,42,250,157,201,158,85,109,220,96,117,25,47,94,208,212,252,104,
 | 
			
		||||
200,40,222,13,155,237,217,179,103,111,12,238,35,250,149,190,197,61,182,160,95,216,114,105,133,207,107,25,111,139,154,222,
 | 
			
		||||
253,249,93,61,198,61,217,6,189,164,133,102,120,77,58,36,218,32,115,147,209,112,136,43,233,57,39,247,123,192,52,126,47,
 | 
			
		||||
250,177,40,47,8,33,156,54,153,2,133,71,44,147,53,10,195,22,14,18,133,54,57,68,141,109,54,75,141,119,88,226,110,182,106,
 | 
			
		||||
172,102,216,50,191,98,44,216,140,182,47,162,173,101,26,95,54,26,155,119,25,23,236,134,122,248,94,191,131,28,134,195,196,
 | 
			
		||||
136,143,138,1,122,205,41,238,192,64,150,8,223,52,139,88,249,115,1,155,102,213,156,66,15,88,230,139,226,57,241,10,87,118,
 | 
			
		||||
155,5,95,55,68,200,52,161,169,117,191,209,208,100,132,171,109,211,46,88,224,48,29,5,59,45,231,87,208,174,101,181,233,184,
 | 
			
		||||
93,148,180,192,69,222,16,179,91,118,154,198,205,198,204,102,12,149,70,108,3,91,231,182,118,116,111,218,14,219,105,156,
 | 
			
		||||
135,133,64,75,135,195,185,211,116,29,17,211,165,148,48,29,100,120,170,33,4,17,219,85,67,239,194,254,213,195,75,134,197,
 | 
			
		||||
140,105,166,128,141,254,203,22,139,187,107,248,201,248,147,104,132,121,109,163,133,141,140,29,90,211,27,218,115,254,229,
 | 
			
		||||
54,13,119,211,95,45,54,108,245,146,157,177,133,198,158,234,253,237,157,177,150,26,250,155,45,94,100,239,48,137,77,17,162,
 | 
			
		||||
27,45,233,191,242,243,18,199,212,186,59,156,226,16,236,184,215,116,98,202,75,238,149,139,231,55,141,55,133,184,237,54,
 | 
			
		||||
211,250,178,225,195,60,239,54,4,166,107,222,105,136,157,171,77,251,46,99,94,76,184,109,187,197,97,55,240,82,96,150,150,
 | 
			
		||||
109,219,14,99,164,218,118,46,112,8,88,219,226,201,26,23,116,161,194,97,148,175,98,41,246,54,167,113,183,241,186,112,54,
 | 
			
		||||
249,77,113,135,33,16,81,208,215,109,188,94,190,157,205,7,62,201,203,37,4,175,150,177,187,186,214,216,221,180,200,54,12,
 | 
			
		||||
239,42,68,160,79,58,48,42,103,241,5,85,244,176,67,60,37,247,146,233,66,91,12,237,144,16,126,211,249,128,176,107,204,130,
 | 
			
		||||
151,196,233,207,239,245,63,98,218,28,168,86,155,214,213,98,222,109,194,101,59,155,49,150,234,221,60,200,123,204,66,248,
 | 
			
		||||
221,189,162,196,107,23,250,141,209,106,12,212,190,213,114,191,195,235,185,179,165,251,244,165,182,123,161,154,12,175,137,
 | 
			
		||||
93,176,136,231,225,112,57,10,28,133,198,100,151,93,200,242,244,174,30,3,92,22,189,63,122,224,40,111,88,251,90,163,230,22,
 | 
			
		||||
219,244,35,24,162,103,140,237,17,83,168,97,16,134,177,223,178,101,31,33,135,217,210,221,39,29,195,14,181,74,227,89,116,
 | 
			
		||||
141,154,221,146,3,22,105,199,130,215,151,172,49,133,156,149,117,159,113,250,18,63,111,28,118,131,3,162,100,154,191,14,
 | 
			
		||||
158,208,221,51,140,230,176,188,191,10,6,122,208,161,98,195,31,45,131,157,24,165,63,89,114,153,233,74,27,195,112,54,169,
 | 
			
		||||
97,24,51,118,24,227,213,75,98,70,113,19,166,115,171,240,122,101,39,45,215,183,204,164,67,82,176,56,70,111,217,210,33,54,
 | 
			
		||||
211,87,212,158,53,130,59,140,225,234,37,7,100,44,178,209,241,217,182,1,47,128,29,121,114,176,64,55,142,139,213,150,245,
 | 
			
		||||
78,110,236,231,247,217,38,124,28,134,132,15,192,243,97,181,26,83,112,36,125,194,48,239,20,95,21,87,168,224,223,66,207,27,
 | 
			
		||||
226,98,195,89,221,49,243,190,102,147,190,100,52,210,47,57,200,98,115,154,244,59,209,246,13,155,186,17,195,110,118,97,103,
 | 
			
		||||
52,211,28,118,223,207,186,196,151,224,239,8,118,251,133,105,120,154,224,42,231,46,89,189,7,222,117,169,172,41,222,223,96,
 | 
			
		||||
156,95,173,56,70,80,252,220,168,104,52,230,26,151,10,171,224,61,81,94,104,156,10,78,149,85,254,225,114,187,124,168,220,
 | 
			
		||||
165,30,237,114,81,254,17,48,154,202,11,164,104,125,129,3,178,21,185,102,51,140,38,150,19,21,243,114,188,114,165,188,65,
 | 
			
		||||
113,12,112,138,115,197,29,57,185,109,198,41,220,214,168,168,175,152,157,233,110,88,246,94,159,235,159,25,103,148,47,207,
 | 
			
		||||
48,156,229,103,130,209,3,180,65,202,157,97,178,212,202,242,141,248,92,150,97,186,48,244,161,114,51,43,171,21,56,202,141,
 | 
			
		||||
60,150,28,139,27,99,137,169,177,56,42,26,42,230,84,212,86,4,42,106,42,234,68,185,37,76,81,96,86,26,248,39,140,142,125,
 | 
			
		||||
251,172,91,230,204,23,207,204,17,226,239,192,161,70,33,126,5,28,12,10,113,23,112,104,174,16,111,2,247,240,143,174,237,
 | 
			
		||||
181,23,185,72,28,3,108,98,151,67,255,230,70,208,146,139,246,89,135,79,53,47,54,168,71,28,104,18,214,43,77,134,184,178,89,
 | 
			
		||||
136,155,128,123,128,103,129,55,129,187,91,16,241,29,133,234,215,242,104,243,74,203,74,113,85,171,176,222,108,21,226,234,
 | 
			
		||||
54,244,11,188,2,236,107,39,67,184,139,13,113,85,96,8,98,87,183,135,197,61,237,66,252,0,248,21,240,58,112,203,60,33,238,7,
 | 
			
		||||
126,12,60,11,188,9,28,232,32,75,216,94,76,80,112,211,115,208,244,134,142,45,226,209,14,33,158,154,47,196,51,11,160,29,
 | 
			
		||||
184,122,33,57,157,165,101,74,76,255,55,10,217,103,23,26,198,193,78,97,220,180,200,48,14,44,6,186,76,227,234,238,66,227,
 | 
			
		||||
192,146,168,245,66,143,41,238,233,53,196,221,125,134,120,182,15,86,3,61,216,143,33,1,119,15,96,40,203,133,56,2,252,106,
 | 
			
		||||
16,163,15,9,113,120,13,250,2,14,172,53,196,161,181,224,175,67,27,224,224,122,88,192,152,37,248,223,199,208,231,85,27,46,
 | 
			
		||||
20,247,108,16,226,170,33,254,13,158,223,45,220,23,137,3,251,172,23,134,4,42,247,133,69,193,65,224,210,141,148,251,251,69,
 | 
			
		||||
249,191,225,201,252,109,62,254,109,74,230,239,243,241,239,82,50,127,163,143,127,151,18,32,245,119,250,248,183,57,153,191,
 | 
			
		||||
213,231,160,220,223,235,51,189,234,183,52,242,119,83,1,245,183,154,234,75,33,19,80,50,252,255,180,11,175,250,253,61,255,
 | 
			
		||||
127,232,70,64,245,203,127,223,207,212,242,252,255,75,91,1,245,187,27,254,127,170,237,128,26,31,255,127,240,164,245,200,
 | 
			
		||||
31,228,121,21,159,255,174,224,255,1,51,160,178,16,144,80,0,0};
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$MidiDeviceManager;") \
 | 
			
		||||
 STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$BluetoothManager;")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/roli/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode))
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \
 | 
			
		||||
 METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \
 | 
			
		||||
 METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") \
 | 
			
		||||
 METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") \
 | 
			
		||||
 METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \
 | 
			
		||||
 METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/roli/juce/JuceMidiSupport$MidiDeviceManager", 23)
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 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
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/roli/juce/JuceMidiSupport$JuceMidiPort", 23)
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class AndroidMidiInput
 | 
			
		||||
@ -51,10 +373,10 @@ public:
 | 
			
		||||
        : juceMidiInput (midiInput),
 | 
			
		||||
          callback (midiInputCallback),
 | 
			
		||||
          midiConcatenator (2048),
 | 
			
		||||
          javaMidiDevice (getEnv()->CallObjectMethod (deviceManager,
 | 
			
		||||
                                                      MidiDeviceManager.openMidiInputPortWithJuceIndex,
 | 
			
		||||
                                                      (jint) portIdx,
 | 
			
		||||
                                                      (jlong) this))
 | 
			
		||||
          javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager,
 | 
			
		||||
                                                                        MidiDeviceManager.openMidiInputPortWithJuceIndex,
 | 
			
		||||
                                                                        (jint) portIdx,
 | 
			
		||||
                                                                        (jlong) this)))
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -86,10 +408,12 @@ public:
 | 
			
		||||
        callback = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
 | 
			
		||||
    void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        jassert (byteArray != nullptr);
 | 
			
		||||
        jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr);
 | 
			
		||||
        jbyte* data = env->GetByteArrayElements (byteArray, nullptr);
 | 
			
		||||
 | 
			
		||||
        HeapBlock<uint8> buffer (static_cast<size_t> (len));
 | 
			
		||||
        std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len));
 | 
			
		||||
@ -98,7 +422,15 @@ public:
 | 
			
		||||
                                       len, static_cast<double> (timestamp) * 1.0e-9,
 | 
			
		||||
                                       juceMidiInput, *callback);
 | 
			
		||||
 | 
			
		||||
        getEnv()->ReleaseByteArrayElements (byteArray, data, 0);
 | 
			
		||||
        env->ReleaseByteArrayElements (byteArray, data, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray,
 | 
			
		||||
                               jint offset, jint len, jlong timestamp)
 | 
			
		||||
    {
 | 
			
		||||
        auto* myself = reinterpret_cast<AndroidMidiInput*> (host);
 | 
			
		||||
 | 
			
		||||
        myself->handleMidi (byteArray, offset, len, timestamp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
@ -112,7 +444,7 @@ private:
 | 
			
		||||
class AndroidMidiOutput
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AndroidMidiOutput (jobject midiDevice)
 | 
			
		||||
    AndroidMidiOutput (const LocalRef<jobject>& midiDevice)
 | 
			
		||||
        : javaMidiDevice (midiDevice)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
@ -138,24 +470,19 @@ 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);
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 CALLBACK (AndroidMidiInput::handleReceive, "handleReceive", "(J[BIIJ)V" )
 | 
			
		||||
 | 
			
		||||
    reinterpret_cast<AndroidMidiInput*> (host)->receive (byteArray, offset, count, timestamp);
 | 
			
		||||
}
 | 
			
		||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/roli/juce/JuceMidiSupport$JuceMidiInputPort", 23)
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class AndroidMidiDeviceManager
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AndroidMidiDeviceManager()
 | 
			
		||||
        : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager))
 | 
			
		||||
        : deviceManager (LocalRef<jobject>(getEnv()->CallStaticObjectMethod (JuceMidiSupport, JuceMidiSupport.getAndroidMidiDeviceManager, getAppContext().get())))
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -187,7 +514,7 @@ public:
 | 
			
		||||
        {
 | 
			
		||||
            jobjectArray jDevices
 | 
			
		||||
                = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices
 | 
			
		||||
                                                                  : MidiDeviceManager.getJuceAndroidMidiOutputDevices);
 | 
			
		||||
                                                                       : MidiDeviceManager.getJuceAndroidMidiOutputDevices);
 | 
			
		||||
 | 
			
		||||
            // Create a local reference as converting this
 | 
			
		||||
            // to a JUCE string will call into JNI
 | 
			
		||||
@ -215,7 +542,7 @@ public:
 | 
			
		||||
    {
 | 
			
		||||
        if (jobject dm = deviceManager.get())
 | 
			
		||||
            if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx))
 | 
			
		||||
                return new AndroidMidiOutput (javaMidiPort);
 | 
			
		||||
                return new AndroidMidiOutput (LocalRef<jobject>(javaMidiPort));
 | 
			
		||||
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
@ -227,18 +554,23 @@ private:
 | 
			
		||||
//==============================================================================
 | 
			
		||||
StringArray MidiOutput::getDevices()
 | 
			
		||||
{
 | 
			
		||||
    AndroidMidiDeviceManager manager;
 | 
			
		||||
    return manager.getDevices (false);
 | 
			
		||||
    if (getAndroidSDKVersion() >= 23)
 | 
			
		||||
    {
 | 
			
		||||
        AndroidMidiDeviceManager manager;
 | 
			
		||||
        return manager.getDevices (false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MidiOutput::getDefaultDeviceIndex()
 | 
			
		||||
{
 | 
			
		||||
    return 0;
 | 
			
		||||
    return (getAndroidSDKVersion() >= 23 ? 0 : -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MidiOutput* MidiOutput::openDevice (int index)
 | 
			
		||||
{
 | 
			
		||||
    if (index < 0)
 | 
			
		||||
    if (index < 0 || getAndroidSDKVersion() < 23)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    AndroidMidiDeviceManager manager;
 | 
			
		||||
@ -295,18 +627,23 @@ MidiInput::MidiInput (const String& nm)  : name (nm)
 | 
			
		||||
 | 
			
		||||
StringArray MidiInput::getDevices()
 | 
			
		||||
{
 | 
			
		||||
    AndroidMidiDeviceManager manager;
 | 
			
		||||
    return manager.getDevices (true);
 | 
			
		||||
    if (getAndroidSDKVersion() >= 23)
 | 
			
		||||
    {
 | 
			
		||||
        AndroidMidiDeviceManager manager;
 | 
			
		||||
        return manager.getDevices (true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MidiInput::getDefaultDeviceIndex()
 | 
			
		||||
{
 | 
			
		||||
    return 0;
 | 
			
		||||
    return (getAndroidSDKVersion() >= 23 ? 0 : -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback)
 | 
			
		||||
{
 | 
			
		||||
    if (index < 0)
 | 
			
		||||
    if (getAndroidSDKVersion() < 23 || index < 0)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    AndroidMidiDeviceManager manager;
 | 
			
		||||
 | 
			
		||||
@ -125,6 +125,12 @@ struct OboeAudioIODeviceBufferHelpers<float>
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename Type>
 | 
			
		||||
static String getOboeString (const Type& value)
 | 
			
		||||
{
 | 
			
		||||
    return String (oboe::convertToText (value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class OboeAudioIODevice  : public AudioIODevice
 | 
			
		||||
{
 | 
			
		||||
@ -272,7 +278,7 @@ public:
 | 
			
		||||
        // by default. We need to be more conservative on other devices
 | 
			
		||||
        // as they may be low-latency, but still have a crappy CPU.
 | 
			
		||||
        return (isProAudioDevice() ? 1 : 6)
 | 
			
		||||
                 * defaultBufferSizeIsMultipleOfNative * getNativeBufferSize();
 | 
			
		||||
                 * getNativeBufferSize();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    double getCurrentSampleRate() override
 | 
			
		||||
@ -391,9 +397,9 @@ private:
 | 
			
		||||
        // Setting nullptr callback is allowed only when playback is stopped.
 | 
			
		||||
        jassert (callbackToUse != nullptr);
 | 
			
		||||
 | 
			
		||||
        while (true)
 | 
			
		||||
        for (;;)
 | 
			
		||||
        {
 | 
			
		||||
            AudioIODeviceCallback* old = callback.get();
 | 
			
		||||
            auto old = callback.get();
 | 
			
		||||
 | 
			
		||||
            if (old == callbackToUse)
 | 
			
		||||
                break;
 | 
			
		||||
@ -409,7 +415,7 @@ private:
 | 
			
		||||
    void process (const float** inputChannelData, int numInputChannels,
 | 
			
		||||
                  float** outputChannelData, int numOutputChannels, int32_t numFrames)
 | 
			
		||||
    {
 | 
			
		||||
        if (AudioIODeviceCallback* cb = callback.exchange (nullptr))
 | 
			
		||||
        if (auto* cb = callback.exchange (nullptr))
 | 
			
		||||
        {
 | 
			
		||||
            cb->audioDeviceIOCallback (inputChannelData, numInputChannels,
 | 
			
		||||
                                       outputChannelData, numOutputChannels, numFrames);
 | 
			
		||||
@ -438,8 +444,8 @@ private:
 | 
			
		||||
 | 
			
		||||
        ~OboeStream()
 | 
			
		||||
        {
 | 
			
		||||
            // AudioStreamCallback can only be deleted when stream has been closed
 | 
			
		||||
            close();
 | 
			
		||||
            delete stream;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool openedOk() const noexcept
 | 
			
		||||
@ -458,37 +464,47 @@ private:
 | 
			
		||||
                int64 timeoutNanos = 1000 * oboe::kNanosPerMillisecond;
 | 
			
		||||
 | 
			
		||||
                auto startResult = stream->requestStart();
 | 
			
		||||
                JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + String (oboe::convertToText (startResult)));
 | 
			
		||||
                JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + getOboeString (startResult));
 | 
			
		||||
 | 
			
		||||
                startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos);
 | 
			
		||||
                JUCE_OBOE_LOG ("Starting Oboe stream with result: " + String (oboe::convertToText (startResult));
 | 
			
		||||
                     + "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
 | 
			
		||||
                     + "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
 | 
			
		||||
                     + "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
 | 
			
		||||
                     + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 | 
			
		||||
                     + "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
 | 
			
		||||
                     + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
 | 
			
		||||
                     + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
 | 
			
		||||
                     + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
 | 
			
		||||
                     + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?"))
 | 
			
		||||
                     + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
 | 
			
		||||
                     + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
 | 
			
		||||
                     + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
 | 
			
		||||
                     + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency))
 | 
			
		||||
                     + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
 | 
			
		||||
 | 
			
		||||
                JUCE_OBOE_LOG ("Starting Oboe stream with result: " + getOboeString (startResult);
 | 
			
		||||
                                 + "\nUses AAudio = " + String ((int) stream->usesAAudio())
 | 
			
		||||
                                 + "\nDirection = " + getOboeString (stream->getDirection())
 | 
			
		||||
                                 + "\nSharingMode = " + getOboeString (stream->getSharingMode())
 | 
			
		||||
                                 + "\nChannelCount = " + String (stream->getChannelCount())
 | 
			
		||||
                                 + "\nFormat = " + getOboeString (stream->getFormat())
 | 
			
		||||
                                 + "\nSampleRate = " + String (stream->getSampleRate())
 | 
			
		||||
                                 + "\nBufferSizeInFrames = " + String (stream->getBufferSizeInFrames())
 | 
			
		||||
                                 + "\nBufferCapacityInFrames = " + String (stream->getBufferCapacityInFrames())
 | 
			
		||||
                                 + "\nFramesPerBurst = " + String (stream->getFramesPerBurst())
 | 
			
		||||
                                 + "\nFramesPerCallback = " + String (stream->getFramesPerCallback())
 | 
			
		||||
                                 + "\nBytesPerFrame = " + String (stream->getBytesPerFrame())
 | 
			
		||||
                                 + "\nBytesPerSample = " + String (stream->getBytesPerSample())
 | 
			
		||||
                                 + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency)
 | 
			
		||||
                                 + "\ngetDeviceId = " + String (stream->getDeviceId()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        oboe::AudioStream* getNativeStream()
 | 
			
		||||
        oboe::AudioStream* getNativeStream() const
 | 
			
		||||
        {
 | 
			
		||||
            jassert (openedOk());
 | 
			
		||||
 | 
			
		||||
            return stream;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int getXRunCount() const
 | 
			
		||||
        {
 | 
			
		||||
            return stream != nullptr ? stream->getXRunCount() : 0;
 | 
			
		||||
            if (stream != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                auto count = stream->getXRunCount();
 | 
			
		||||
 | 
			
		||||
                if (count)
 | 
			
		||||
                    return count.value();
 | 
			
		||||
 | 
			
		||||
                JUCE_OBOE_LOG ("Failed to get Xrun count: " + getOboeString (count.error()));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
@ -498,50 +514,50 @@ private:
 | 
			
		||||
                   int32 sampleRate, int32 bufferSize,
 | 
			
		||||
                   oboe::AudioStreamCallback* callback = nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            oboe::DefaultStreamValues::FramesPerBurst = getDefaultFramesPerBurst();
 | 
			
		||||
 | 
			
		||||
            oboe::AudioStreamBuilder builder;
 | 
			
		||||
 | 
			
		||||
            if (deviceId != -1)
 | 
			
		||||
                builder.setDeviceId (deviceId);
 | 
			
		||||
 | 
			
		||||
            static int defaultFramesPerBurst = getDefaultFramesPerBurst();
 | 
			
		||||
 | 
			
		||||
            // Note: letting OS to choose the buffer capacity & frames per callback.
 | 
			
		||||
            builder.setDirection (direction);
 | 
			
		||||
            builder.setSharingMode (sharingMode);
 | 
			
		||||
            builder.setChannelCount (channelCount);
 | 
			
		||||
            builder.setFormat (format);
 | 
			
		||||
            builder.setSampleRate (sampleRate);
 | 
			
		||||
            builder.setDefaultFramesPerBurst ((int32) defaultFramesPerBurst);
 | 
			
		||||
            builder.setPerformanceMode (oboe::PerformanceMode::LowLatency);
 | 
			
		||||
            builder.setCallback (callback);
 | 
			
		||||
 | 
			
		||||
            JUCE_OBOE_LOG (String ("Preparing Oboe stream with params:")
 | 
			
		||||
                 + "\nAAudio supported = " + String (int (builder.isAAudioSupported()))
 | 
			
		||||
                 + "\nAPI = " + String (oboe::convertToText (builder.getAudioApi()))
 | 
			
		||||
                 + "\nAPI = " + getOboeString (builder.getAudioApi())
 | 
			
		||||
                 + "\nDeviceId = " + String (deviceId)
 | 
			
		||||
                 + "\nDirection = " + String (oboe::convertToText (direction))
 | 
			
		||||
                 + "\nSharingMode = " + String (oboe::convertToText (sharingMode))
 | 
			
		||||
                 + "\nDirection = " + getOboeString (direction)
 | 
			
		||||
                 + "\nSharingMode = " + getOboeString (sharingMode)
 | 
			
		||||
                 + "\nChannelCount = " + String (channelCount)
 | 
			
		||||
                 + "\nFormat = " + String (oboe::convertToText (format))
 | 
			
		||||
                 + "\nFormat = " + getOboeString (format)
 | 
			
		||||
                 + "\nSampleRate = " + String (sampleRate)
 | 
			
		||||
                 + "\nBufferSizeInFrames = " + String (bufferSize)
 | 
			
		||||
                 + "\nFramesPerBurst = " + String (defaultFramesPerBurst)
 | 
			
		||||
                 + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency)));
 | 
			
		||||
                 + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency));
 | 
			
		||||
 | 
			
		||||
            openResult = builder.openStream (&stream);
 | 
			
		||||
            JUCE_OBOE_LOG ("Building Oboe stream with result: " + String (oboe::convertToText (openResult))
 | 
			
		||||
                 + "\nStream state = " + (stream != nullptr ? String (oboe::convertToText (stream->getState())) : String ("?")));
 | 
			
		||||
            JUCE_OBOE_LOG ("Building Oboe stream with result: " + getOboeString (openResult)
 | 
			
		||||
                 + "\nStream state = " + (stream != nullptr ? getOboeString (stream->getState()) : String ("?")));
 | 
			
		||||
 | 
			
		||||
            if (stream != nullptr)
 | 
			
		||||
            if (stream != nullptr && bufferSize != 0)
 | 
			
		||||
            {
 | 
			
		||||
                JUCE_OBOE_LOG ("Setting the bufferSizeInFrames to " + String (bufferSize));
 | 
			
		||||
                stream->setBufferSizeInFrames (bufferSize);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            JUCE_OBOE_LOG (String ("Stream details:")
 | 
			
		||||
                 + "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
 | 
			
		||||
                 + "\nDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?"))
 | 
			
		||||
                 + "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
 | 
			
		||||
                 + "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
 | 
			
		||||
                 + "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?"))
 | 
			
		||||
                 + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
 | 
			
		||||
                 + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 | 
			
		||||
                 + "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
 | 
			
		||||
                 + "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?"))
 | 
			
		||||
                 + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
 | 
			
		||||
                 + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
 | 
			
		||||
                 + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
 | 
			
		||||
@ -549,7 +565,7 @@ private:
 | 
			
		||||
                 + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
 | 
			
		||||
                 + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
 | 
			
		||||
                 + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
 | 
			
		||||
                 + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency)));
 | 
			
		||||
                 + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void close()
 | 
			
		||||
@ -557,27 +573,10 @@ private:
 | 
			
		||||
            if (stream != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                oboe::Result result = stream->close();
 | 
			
		||||
                JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + String (oboe::convertToText (result)));
 | 
			
		||||
                JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + getOboeString (result));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int getDefaultFramesPerBurst() const
 | 
			
		||||
        {
 | 
			
		||||
            // NB: this function only works for inbuilt speakers and headphones
 | 
			
		||||
            auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
            auto audioManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
 | 
			
		||||
                                                                          JuceAppActivity.getSystemService,
 | 
			
		||||
                                                                          javaString ("audio").get()));
 | 
			
		||||
 | 
			
		||||
            auto propertyJavaString = javaString ("android.media.property.OUTPUT_FRAMES_PER_BUFFER");
 | 
			
		||||
 | 
			
		||||
            auto framesPerBurstString = LocalRef<jstring> ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty,
 | 
			
		||||
                                                                                                        propertyJavaString.get()));
 | 
			
		||||
 | 
			
		||||
            return framesPerBurstString != 0 ? env->CallStaticIntMethod (JavaInteger, JavaInteger.parseInt, framesPerBurstString.get(), 10) : 192;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        oboe::AudioStream* stream = nullptr;
 | 
			
		||||
        oboe::Result openResult;
 | 
			
		||||
    };
 | 
			
		||||
@ -669,20 +668,14 @@ private:
 | 
			
		||||
        void checkStreamSetup (OboeStream* stream, int deviceId, int numChannels, int sampleRate,
 | 
			
		||||
                               int bufferSize, oboe::AudioFormat format)
 | 
			
		||||
        {
 | 
			
		||||
            auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr;
 | 
			
		||||
 | 
			
		||||
            if (nativeStream != nullptr)
 | 
			
		||||
            if (auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                ignoreUnused (deviceId, numChannels, sampleRate, bufferSize);
 | 
			
		||||
                ignoreUnused (streamFormat, bitDepth);
 | 
			
		||||
 | 
			
		||||
                jassert (deviceId = nativeStream->getDeviceId());
 | 
			
		||||
                jassert (numChannels = nativeStream->getChannelCount());
 | 
			
		||||
                jassert (sampleRate == nativeStream->getSampleRate());
 | 
			
		||||
                jassert (numChannels == nativeStream->getChannelCount());
 | 
			
		||||
                jassert (sampleRate == 0 || sampleRate == nativeStream->getSampleRate());
 | 
			
		||||
                jassert (format == nativeStream->getFormat());
 | 
			
		||||
 | 
			
		||||
                if (nativeStream->usesAAudio())
 | 
			
		||||
                    jassert (bufferSize == nativeStream->getBufferSizeInFrames());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -737,7 +730,8 @@ private:
 | 
			
		||||
 | 
			
		||||
            outputStream->start();
 | 
			
		||||
 | 
			
		||||
            checkIsOutputLatencyDetectionSupported();
 | 
			
		||||
            isInputLatencyDetectionSupported  = isLatencyDetectionSupported (inputStream.get());
 | 
			
		||||
            isOutputLatencyDetectionSupported = isLatencyDetectionSupported (outputStream.get());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void stop() override
 | 
			
		||||
@ -752,25 +746,20 @@ private:
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int getOutputLatencyInSamples() override    { return outputLatency; }
 | 
			
		||||
        int getInputLatencyInSamples() override     { return -1; }
 | 
			
		||||
        int getInputLatencyInSamples() override     { return inputLatency; }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        void checkIsOutputLatencyDetectionSupported()
 | 
			
		||||
        bool isLatencyDetectionSupported (OboeStream* stream)
 | 
			
		||||
        {
 | 
			
		||||
            if (! openedOk())
 | 
			
		||||
            {
 | 
			
		||||
                isOutputLatencyDetectionSupported = false;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (stream == nullptr || ! openedOk())
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            auto result = outputStream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, 0, 0);
 | 
			
		||||
            isOutputLatencyDetectionSupported = result != oboe::Result::ErrorUnimplemented;
 | 
			
		||||
            auto result = stream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, 0, 0);
 | 
			
		||||
            return result != oboe::Result::ErrorUnimplemented;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override
 | 
			
		||||
        {
 | 
			
		||||
            attachAndroidJNI();
 | 
			
		||||
 | 
			
		||||
            if (audioCallbackGuard.compareAndSetBool (1, 0))
 | 
			
		||||
            {
 | 
			
		||||
                if (stream == nullptr)
 | 
			
		||||
@ -790,26 +779,28 @@ private:
 | 
			
		||||
 | 
			
		||||
                    if (nativeInputStream->getFormat() != oboe::AudioFormat::I16 && nativeInputStream->getFormat() != oboe::AudioFormat::Float)
 | 
			
		||||
                    {
 | 
			
		||||
                        JUCE_OBOE_LOG ("Unsupported input stream audio format: " + String (oboe::convertToText (nativeInputStream->getFormat())));
 | 
			
		||||
                        JUCE_OBOE_LOG ("Unsupported input stream audio format: " + getOboeString (nativeInputStream->getFormat()));
 | 
			
		||||
                        jassertfalse;
 | 
			
		||||
                        return oboe::DataCallbackResult::Continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    auto result = inputStream->getNativeStream()->read (inputStreamNativeBuffer.getData(), numFrames, 0);
 | 
			
		||||
 | 
			
		||||
                    if (result >= 0)
 | 
			
		||||
                    if (result)
 | 
			
		||||
                    {
 | 
			
		||||
                        OboeAudioIODeviceBufferHelpers<SampleType>::referAudioBufferDirectlyToOboeIfPossible (inputStreamNativeBuffer.get(),
 | 
			
		||||
                                                                                                              inputStreamSampleBuffer,
 | 
			
		||||
                                                                                                              result);
 | 
			
		||||
                                                                                                              result.value());
 | 
			
		||||
 | 
			
		||||
                        OboeAudioIODeviceBufferHelpers<SampleType>::convertFromOboe (inputStreamNativeBuffer.get(), inputStreamSampleBuffer, result);
 | 
			
		||||
                        OboeAudioIODeviceBufferHelpers<SampleType>::convertFromOboe (inputStreamNativeBuffer.get(), inputStreamSampleBuffer, result.value());
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        // Failed to read from input stream.
 | 
			
		||||
                        jassertfalse;
 | 
			
		||||
                        JUCE_OBOE_LOG ("Failed to read from input stream: " + getOboeString (result.error()));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (isInputLatencyDetectionSupported)
 | 
			
		||||
                        inputLatency = getLatencyFor (*inputStream);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //-----------------
 | 
			
		||||
@ -832,7 +823,7 @@ private:
 | 
			
		||||
                OboeAudioIODeviceBufferHelpers<SampleType>::convertToOboe (outputStreamSampleBuffer, static_cast<SampleType*> (audioData), numFrames);
 | 
			
		||||
 | 
			
		||||
                if (isOutputLatencyDetectionSupported)
 | 
			
		||||
                    calculateOutputLatency();
 | 
			
		||||
                    outputLatency = getLatencyFor (*outputStream);
 | 
			
		||||
 | 
			
		||||
                audioCallbackGuard.set (0);
 | 
			
		||||
            }
 | 
			
		||||
@ -845,10 +836,10 @@ private:
 | 
			
		||||
            ignoreUnused (stream);
 | 
			
		||||
 | 
			
		||||
            JUCE_OBOE_LOG ("\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?"))
 | 
			
		||||
                 + "\nDirection = " + (stream != nullptr ? String (oboe::convertToText (stream->getDirection())) : String ("?"))
 | 
			
		||||
                 + "\nSharingMode = " + (stream != nullptr ? String (oboe::convertToText (stream->getSharingMode())) : String ("?"))
 | 
			
		||||
                 + "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?"))
 | 
			
		||||
                 + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?"))
 | 
			
		||||
                 + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?"))
 | 
			
		||||
                 + "\nFormat = " + (stream != nullptr ? String (oboe::convertToText (stream->getFormat())) : String ("?"))
 | 
			
		||||
                 + "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?"))
 | 
			
		||||
                 + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?"))
 | 
			
		||||
                 + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?"))
 | 
			
		||||
                 + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?"))
 | 
			
		||||
@ -856,34 +847,48 @@ private:
 | 
			
		||||
                 + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?"))
 | 
			
		||||
                 + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?"))
 | 
			
		||||
                 + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?"))
 | 
			
		||||
                 + "\nPerformanceMode = " + String (oboe::convertToText (oboe::PerformanceMode::LowLatency))
 | 
			
		||||
                 + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency)
 | 
			
		||||
                 + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void calculateOutputLatency()
 | 
			
		||||
        int getLatencyFor (OboeStream& stream)
 | 
			
		||||
        {
 | 
			
		||||
            // Sadly, Oboe uses non-portable int64_t (a.k.a. long on LP64 and long long on LLP64)
 | 
			
		||||
            int64_t lastWrittenAndPresentedFrameIndex = 0;
 | 
			
		||||
            int64_t lastFramePresentationTimeNanos = 0;
 | 
			
		||||
            auto& nativeStream = *stream.getNativeStream();
 | 
			
		||||
 | 
			
		||||
            auto result = outputStream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC,
 | 
			
		||||
                                                                         &lastWrittenAndPresentedFrameIndex,
 | 
			
		||||
                                                                         &lastFramePresentationTimeNanos);
 | 
			
		||||
            if (auto latency = nativeStream.calculateLatencyMillis())
 | 
			
		||||
                return static_cast<int> ((latency.value() * sampleRate) / 1000);
 | 
			
		||||
 | 
			
		||||
            // Get the time that a known audio frame was presented.
 | 
			
		||||
            int64_t hardwareFrameIndex = 0;
 | 
			
		||||
            int64_t hardwareFrameHardwareTime = 0;
 | 
			
		||||
 | 
			
		||||
            auto result = nativeStream.getTimestamp (CLOCK_MONOTONIC,
 | 
			
		||||
                                                     &hardwareFrameIndex,
 | 
			
		||||
                                                     &hardwareFrameHardwareTime);
 | 
			
		||||
 | 
			
		||||
            if (result != oboe::Result::OK)
 | 
			
		||||
                return;
 | 
			
		||||
                return 0;
 | 
			
		||||
 | 
			
		||||
            int64_t currentNumFramesWritten = outputStream->getNativeStream()->getFramesWritten();
 | 
			
		||||
            int64_t framesDelta = currentNumFramesWritten - lastWrittenAndPresentedFrameIndex;
 | 
			
		||||
            int64_t timeDeltaNanos = framesDelta * oboe::kNanosPerSecond / sampleRate;
 | 
			
		||||
            // Get counter closest to the app.
 | 
			
		||||
            const bool isOutput = nativeStream.getDirection() == oboe::Direction::Output;
 | 
			
		||||
            const int64_t appFrameIndex = isOutput ? nativeStream.getFramesWritten() : nativeStream.getFramesRead();
 | 
			
		||||
 | 
			
		||||
            int64_t nextPresentationTimeNanos = lastFramePresentationTimeNanos + timeDeltaNanos;
 | 
			
		||||
            int64_t nextFrameWriteTimeNanos = getCurrentTimeNanos();
 | 
			
		||||
            // Assume that the next frame will be processed at the current time
 | 
			
		||||
            using namespace std::chrono;
 | 
			
		||||
            int64_t appFrameAppTime = getCurrentTimeNanos();//duration_cast<nanoseconds> (steady_clock::now().time_since_epoch()).count();
 | 
			
		||||
            int64_t appFrameAppTime2 = duration_cast<nanoseconds> (steady_clock::now().time_since_epoch()).count();
 | 
			
		||||
 | 
			
		||||
            if (nextFrameWriteTimeNanos < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            // Calculate the number of frames between app and hardware
 | 
			
		||||
            int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
 | 
			
		||||
 | 
			
		||||
            outputLatency = (int) ((nextPresentationTimeNanos - nextFrameWriteTimeNanos) * sampleRate / oboe::kNanosPerSecond);
 | 
			
		||||
            // Calculate the time which the next frame will be or was presented
 | 
			
		||||
            int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / sampleRate;
 | 
			
		||||
            int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
 | 
			
		||||
 | 
			
		||||
            // Calculate latency as a difference in time between when the current frame is at the app
 | 
			
		||||
            // and when it is at the hardware.
 | 
			
		||||
            auto latencyNanos = isOutput ? (appFrameHardwareTime - appFrameAppTime) : (appFrameAppTime - appFrameHardwareTime);
 | 
			
		||||
            return static_cast<int> ((latencyNanos  * sampleRate) / oboe::kNanosPerSecond);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int64_t getCurrentTimeNanos()
 | 
			
		||||
@ -901,7 +906,7 @@ private:
 | 
			
		||||
            // only output stream should be the master stream receiving callbacks
 | 
			
		||||
            jassert (stream->getDirection() == oboe::Direction::Output);
 | 
			
		||||
 | 
			
		||||
            JUCE_OBOE_LOG ("Oboe stream onErrorBeforeClose(): " + String (oboe::convertToText (error)));
 | 
			
		||||
            JUCE_OBOE_LOG ("Oboe stream onErrorBeforeClose(): " + getOboeString (error));
 | 
			
		||||
            printStreamDebugInfo (stream);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -910,7 +915,7 @@ private:
 | 
			
		||||
            // only output stream should be the master stream receiving callbacks
 | 
			
		||||
            jassert (stream->getDirection() == oboe::Direction::Output);
 | 
			
		||||
 | 
			
		||||
            JUCE_OBOE_LOG ("Oboe stream onErrorAfterClose(): " + String (oboe::convertToText (error)));
 | 
			
		||||
            JUCE_OBOE_LOG ("Oboe stream onErrorAfterClose(): " + getOboeString (error));
 | 
			
		||||
 | 
			
		||||
            if (error == oboe::Result::ErrorDisconnected)
 | 
			
		||||
            {
 | 
			
		||||
@ -946,7 +951,10 @@ private:
 | 
			
		||||
        Atomic<int> audioCallbackGuard { 0 },
 | 
			
		||||
                    streamRestartGuard { 0 };
 | 
			
		||||
 | 
			
		||||
        bool isOutputLatencyDetectionSupported = true;
 | 
			
		||||
        bool isInputLatencyDetectionSupported = false;
 | 
			
		||||
        int inputLatency = -1;
 | 
			
		||||
 | 
			
		||||
        bool isOutputLatencyDetectionSupported = false;
 | 
			
		||||
        int outputLatency = -1;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -972,32 +980,7 @@ private:
 | 
			
		||||
 | 
			
		||||
    bool running = false;
 | 
			
		||||
 | 
			
		||||
    enum
 | 
			
		||||
    {
 | 
			
		||||
        // These at the moment correspond to OpenSL settings.
 | 
			
		||||
        bufferSizeMultForLowLatency = 4,
 | 
			
		||||
        bufferSizeMultForSlowAudio = 8,
 | 
			
		||||
        defaultBufferSizeIsMultipleOfNative = 1
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static String audioManagerGetProperty (const String& property)
 | 
			
		||||
    {
 | 
			
		||||
        const LocalRef<jstring> jProperty (javaString (property));
 | 
			
		||||
        const LocalRef<jstring> text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty,
 | 
			
		||||
                                                                                   jProperty.get()));
 | 
			
		||||
        if (text.get() != 0)
 | 
			
		||||
            return juceString (text);
 | 
			
		||||
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static bool androidHasSystemFeature (const String& property)
 | 
			
		||||
    {
 | 
			
		||||
        const LocalRef<jstring> jProperty (javaString (property));
 | 
			
		||||
        return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static double getNativeSampleRate()
 | 
			
		||||
    {
 | 
			
		||||
        return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue();
 | 
			
		||||
@ -1014,6 +997,14 @@ private:
 | 
			
		||||
        return androidHasSystemFeature ("android.hardware.audio.pro");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static int getDefaultFramesPerBurst()
 | 
			
		||||
    {
 | 
			
		||||
        // NB: this function only works for inbuilt speakers and headphones
 | 
			
		||||
        auto framesPerBurstString = javaString (audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER"));
 | 
			
		||||
 | 
			
		||||
        return framesPerBurstString != 0 ? getEnv()->CallStaticIntMethod (JavaInteger, JavaInteger.parseInt, framesPerBurstString.get(), 10) : 192;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODevice)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1028,7 +1019,7 @@ OboeAudioIODevice::OboeSessionBase* OboeAudioIODevice::OboeSessionBase::create (
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<OboeSessionBase> session;
 | 
			
		||||
    auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT);
 | 
			
		||||
    auto sdkVersion = getAndroidSDKVersion();
 | 
			
		||||
 | 
			
		||||
    // SDK versions 21 and higher should natively support floating point...
 | 
			
		||||
    if (sdkVersion >= 21)
 | 
			
		||||
@ -1096,7 +1087,7 @@ public:
 | 
			
		||||
                               forInput ? oboe::Direction::Input : oboe::Direction::Output,
 | 
			
		||||
                               oboe::SharingMode::Shared,
 | 
			
		||||
                               forInput ? 1 : 2,
 | 
			
		||||
                               getSdkVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16,
 | 
			
		||||
                               getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16,
 | 
			
		||||
                               (int) OboeAudioIODevice::getNativeSampleRate(),
 | 
			
		||||
                               OboeAudioIODevice::getNativeBufferSize(),
 | 
			
		||||
                               nullptr);
 | 
			
		||||
@ -1115,18 +1106,17 @@ public:
 | 
			
		||||
 | 
			
		||||
    int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
 | 
			
		||||
    {
 | 
			
		||||
        if (device == nullptr)
 | 
			
		||||
            return -1;
 | 
			
		||||
        if (auto oboeDevice = static_cast<OboeAudioIODevice*> (device))
 | 
			
		||||
        {
 | 
			
		||||
            auto oboeDeviceId = asInput ? oboeDevice->inputDeviceId
 | 
			
		||||
                                        : oboeDevice->outputDeviceId;
 | 
			
		||||
 | 
			
		||||
        auto* oboeDevice = static_cast<OboeAudioIODevice*> (device);
 | 
			
		||||
        auto oboeDeviceId = asInput ? oboeDevice->inputDeviceId
 | 
			
		||||
                                    : oboeDevice->outputDeviceId;
 | 
			
		||||
            auto& devices = asInput ? inputDevices : outputDevices;
 | 
			
		||||
 | 
			
		||||
        auto& devices = asInput ? inputDevices : outputDevices;
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < devices.size(); ++i)
 | 
			
		||||
            if (devices.getReference (i).id == oboeDeviceId)
 | 
			
		||||
                return i;
 | 
			
		||||
            for (int i = 0; i < devices.size(); ++i)
 | 
			
		||||
                if (devices.getReference (i).id == oboeDeviceId)
 | 
			
		||||
                    return i;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
@ -1188,8 +1178,8 @@ public:
 | 
			
		||||
        if (audioManagerClass == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        auto audioManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
 | 
			
		||||
                                                                      JuceAppActivity.getSystemService,
 | 
			
		||||
        auto audioManager = LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(),
 | 
			
		||||
                                                                      AndroidContext.getSystemService,
 | 
			
		||||
                                                                      javaString ("audio").get()));
 | 
			
		||||
 | 
			
		||||
        static jmethodID getDevicesMethod = env->GetMethodID (audioManagerClass, "getDevices",
 | 
			
		||||
@ -1231,16 +1221,10 @@ public:
 | 
			
		||||
 | 
			
		||||
    bool supportsDevicesInfo() const
 | 
			
		||||
    {
 | 
			
		||||
        static auto result = getSdkVersion() >= 23;
 | 
			
		||||
        static auto result = getAndroidSDKVersion() >= 23;
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int getSdkVersion() const
 | 
			
		||||
    {
 | 
			
		||||
        static auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT);
 | 
			
		||||
        return sdkVersion;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void addDevice (const LocalRef<jobject>& device, JNIEnv* env)
 | 
			
		||||
    {
 | 
			
		||||
        auto deviceClass = LocalRef<jclass> ((jclass) env->FindClass ("android/media/AudioDeviceInfo"));
 | 
			
		||||
@ -1397,7 +1381,7 @@ public:
 | 
			
		||||
        return testStream != nullptr && testStream->openedOk();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pthread_t startThread (void* (*entry) (void*), void* userPtr)
 | 
			
		||||
    pthread_t startThread (void*(*entry)(void*), void* userPtr)
 | 
			
		||||
    {
 | 
			
		||||
        pthread_mutex_lock (&threadReadyMutex);
 | 
			
		||||
 | 
			
		||||
@ -1437,26 +1421,16 @@ public:
 | 
			
		||||
 | 
			
		||||
    void onErrorBeforeClose (oboe::AudioStream*, oboe::Result error) override
 | 
			
		||||
    {
 | 
			
		||||
        JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + String (oboe::convertToText (error)));
 | 
			
		||||
        JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + getOboeString (error));
 | 
			
		||||
        ignoreUnused (error);
 | 
			
		||||
        jassertfalse;  // Should never get here!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override
 | 
			
		||||
    void onErrorAfterClose (oboe::AudioStream*, oboe::Result error) override
 | 
			
		||||
    {
 | 
			
		||||
        JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + String (oboe::convertToText (error)));
 | 
			
		||||
 | 
			
		||||
        if (error == oboe::Result::ErrorDisconnected)
 | 
			
		||||
        {
 | 
			
		||||
            testStream.reset();
 | 
			
		||||
            testStream.reset (new OboeStream (-1,
 | 
			
		||||
                                              oboe::Direction::Output,
 | 
			
		||||
                                              oboe::SharingMode::Exclusive,
 | 
			
		||||
                                              1,
 | 
			
		||||
                                              formatUsed,
 | 
			
		||||
                                              (int) OboeAudioIODevice::getNativeSampleRate(),
 | 
			
		||||
                                              OboeAudioIODevice::getNativeBufferSize(),
 | 
			
		||||
                                              this));
 | 
			
		||||
            testStream->start();
 | 
			
		||||
        }
 | 
			
		||||
        JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + getOboeString (error));
 | 
			
		||||
        ignoreUnused (error);
 | 
			
		||||
        jassertfalse;  // Should never get here!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
@ -1480,10 +1454,7 @@ pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    auto threadID = thread->startThread (entry, userPtr);
 | 
			
		||||
 | 
			
		||||
    // the thread will de-allocate itself
 | 
			
		||||
    thread.release();
 | 
			
		||||
 | 
			
		||||
    thread.release();  // the thread will de-allocate itself
 | 
			
		||||
    return threadID;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,10 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidAudioManager, "android/media/AudioManager")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#ifndef SL_ANDROID_DATAFORMAT_PCM_EX
 | 
			
		||||
 #define SL_ANDROID_DATAFORMAT_PCM_EX                   ((SLuint32) 0x00000004)
 | 
			
		||||
@ -60,6 +64,22 @@ SLInterfaceID_ IntfIID<SLRecordItf_>::iid                   = { 0xc5657aa0, 0xdd
 | 
			
		||||
SLInterfaceID_ IntfIID<SLAndroidSimpleBufferQueueItf_>::iid = { 0x198e4940, 0xc5d7, 0x11df, 0xa2a6, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} };
 | 
			
		||||
SLInterfaceID_ IntfIID<SLAndroidConfigurationItf_>::iid     = { 0x89f6a7e0, 0xbeac, 0x11df, 0x8b5c, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} };
 | 
			
		||||
 | 
			
		||||
template <typename SLObjectType>
 | 
			
		||||
static void destroyObject (SLObjectType object)
 | 
			
		||||
{
 | 
			
		||||
    if (object != nullptr && *object != nullptr)
 | 
			
		||||
        (*object)->Destroy (object);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct ContainerDeletePolicy<const SLObjectItf_* const>
 | 
			
		||||
{
 | 
			
		||||
    static void destroy (SLObjectItf object)
 | 
			
		||||
    {
 | 
			
		||||
        destroyObject (object);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// Some life-time and type management of OpenSL objects
 | 
			
		||||
class SlObjectRef
 | 
			
		||||
@ -67,27 +87,34 @@ class SlObjectRef
 | 
			
		||||
public:
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    SlObjectRef() noexcept {}
 | 
			
		||||
    SlObjectRef (const SlObjectRef&  obj) noexcept : cb (obj.cb) {}
 | 
			
		||||
    SlObjectRef (SlObjectRef&& obj) noexcept : cb (static_cast<ReferenceCountedObjectPtr<ControlBlock>&&> (obj.cb)) { obj.cb = nullptr; }
 | 
			
		||||
    SlObjectRef (const SlObjectRef& obj) noexcept : cb (obj.cb) {}
 | 
			
		||||
    SlObjectRef (SlObjectRef&& obj) noexcept : cb (std::move (obj.cb)) { obj.cb = nullptr; }
 | 
			
		||||
    explicit SlObjectRef (SLObjectItf o) : cb (new ControlBlock (o)) {}
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    SlObjectRef& operator=(const SlObjectRef& r)   noexcept { cb = r.cb; return *this; }
 | 
			
		||||
    SlObjectRef& operator=(SlObjectRef&& r)        noexcept { cb = static_cast<ReferenceCountedObjectPtr<ControlBlock>&&> (r.cb); r.cb = nullptr; return *this; }
 | 
			
		||||
    SlObjectRef& operator=(std::nullptr_t) noexcept   { cb = nullptr; return *this; }
 | 
			
		||||
    SlObjectRef& operator= (const SlObjectRef& r) noexcept  { cb = r.cb; return *this; }
 | 
			
		||||
    SlObjectRef& operator= (SlObjectRef&& r) noexcept       { cb = std::move (r.cb); r.cb = nullptr; return *this; }
 | 
			
		||||
    SlObjectRef& operator= (std::nullptr_t) noexcept        { cb = nullptr; return *this; }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    const SLObjectItf_* const  operator*()  noexcept  { return *cb->ptr.get(); }
 | 
			
		||||
    SLObjectItf operator->() noexcept  { return (cb == nullptr ? nullptr :  cb->ptr.get()); }
 | 
			
		||||
    operator SLObjectItf()   noexcept  { return (cb == nullptr ? nullptr :  cb->ptr.get()); }
 | 
			
		||||
    const SLObjectItf_* operator*() noexcept                { return *cb->ptr.get(); }
 | 
			
		||||
    SLObjectItf operator->() noexcept                       { return (cb == nullptr ? nullptr :  cb->ptr.get()); }
 | 
			
		||||
    operator SLObjectItf() noexcept                         { return (cb == nullptr ? nullptr :  cb->ptr.get()); }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    bool operator== (nullptr_t) const noexcept  { return (cb == nullptr || cb->ptr == nullptr); }
 | 
			
		||||
    bool operator!= (nullptr_t) const noexcept  { return (cb != nullptr && cb->ptr != nullptr); }
 | 
			
		||||
    bool operator== (nullptr_t) const noexcept              { return (cb == nullptr || cb->ptr == nullptr); }
 | 
			
		||||
    bool operator!= (nullptr_t) const noexcept              { return (cb != nullptr && cb->ptr != nullptr); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    struct ControlBlock : ReferenceCountedObject { std::unique_ptr<const SLObjectItf_* const> ptr; ControlBlock() {} ControlBlock (SLObjectItf o) : ptr (o) {} };
 | 
			
		||||
    struct ControlBlock : ReferenceCountedObject
 | 
			
		||||
    {
 | 
			
		||||
        ControlBlock() = default;
 | 
			
		||||
        ControlBlock (SLObjectItf o) : ptr (o) {}
 | 
			
		||||
 | 
			
		||||
        std::unique_ptr<const SLObjectItf_* const> ptr;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ReferenceCountedObjectPtr<ControlBlock> cb;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -96,53 +123,53 @@ class SlRef : public SlObjectRef
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    SlRef() noexcept : type (nullptr) {}
 | 
			
		||||
    SlRef (SlRef& r)  noexcept : SlObjectRef (r), type (r.type) {}
 | 
			
		||||
    SlRef (SlRef&& r) noexcept : SlObjectRef (static_cast<SlRef&&> (r)), type (r.type) { r.type = nullptr; }
 | 
			
		||||
    SlRef() noexcept {}
 | 
			
		||||
    SlRef (const SlRef& r) noexcept : SlObjectRef (r), type (r.type) {}
 | 
			
		||||
    SlRef (SlRef&& r) noexcept : SlObjectRef (std::move (r)), type (r.type) { r.type = nullptr; }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    SlRef& operator= (const SlRef& r)  noexcept { SlObjectRef::operator= (r); type = r.type; return *this; }
 | 
			
		||||
    SlRef& operator= (SlRef&& r) noexcept { SlObjectRef::operator= (static_cast<SlObjectRef&&> (r)); type = r.type; r.type = nullptr; return *this; }
 | 
			
		||||
    SlRef& operator= (std::nullptr_t) noexcept { SlObjectRef::operator= (nullptr); type = nullptr; return *this; }
 | 
			
		||||
    SlRef& operator= (SlRef&& r) noexcept       { SlObjectRef::operator= (std::move (r)); type = r.type; r.type = nullptr; return *this; }
 | 
			
		||||
    SlRef& operator= (std::nullptr_t) noexcept  { SlObjectRef::operator= (nullptr); type = nullptr; return *this; }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    T* const operator*()  noexcept { return *type; }
 | 
			
		||||
    T* const * operator->() noexcept { return type; }
 | 
			
		||||
    operator T* const *()   noexcept { return type; }
 | 
			
		||||
    T* const operator*() noexcept               { return *type; }
 | 
			
		||||
    T* const* operator->() noexcept             { return type; }
 | 
			
		||||
    operator T* const*() noexcept               { return type; }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static SlRef cast (SlObjectRef&  base) { return SlRef (base); }
 | 
			
		||||
    static SlRef cast (SlObjectRef&& base) { return SlRef (static_cast<SlObjectRef&&> (base)); }
 | 
			
		||||
    static SlRef cast (SlObjectRef&  base)      { return SlRef (base); }
 | 
			
		||||
    static SlRef cast (SlObjectRef&& base)      { return SlRef (std::move (base)); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    SlRef (SlObjectRef& base) : SlObjectRef (base)
 | 
			
		||||
    {
 | 
			
		||||
        SLObjectItf obj = SlObjectRef::operator->();
 | 
			
		||||
        SLresult err = (*obj)->GetInterface (obj, &IntfIID<T>::iid, &type);
 | 
			
		||||
        if (type == nullptr || err != SL_RESULT_SUCCESS)
 | 
			
		||||
            *this = nullptr;
 | 
			
		||||
        if (auto obj = SlObjectRef::operator->())
 | 
			
		||||
        {
 | 
			
		||||
            auto err = (*obj)->GetInterface (obj, &IntfIID<T>::iid, &type);
 | 
			
		||||
 | 
			
		||||
            if (type != nullptr && err == SL_RESULT_SUCCESS)
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        *this = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SlRef (SlObjectRef&& base) : SlObjectRef (static_cast<SlObjectRef&&> (base))
 | 
			
		||||
    SlRef (SlObjectRef&& base) : SlObjectRef (std::move (base))
 | 
			
		||||
    {
 | 
			
		||||
        SLObjectItf obj = SlObjectRef::operator->();
 | 
			
		||||
        SLresult err = (*obj)->GetInterface (obj, &IntfIID<T>::iid, &type);
 | 
			
		||||
        base = nullptr;
 | 
			
		||||
        if (auto obj = SlObjectRef::operator->())
 | 
			
		||||
        {
 | 
			
		||||
            auto err = (*obj)->GetInterface (obj, &IntfIID<T>::iid, &type);
 | 
			
		||||
            base = nullptr;
 | 
			
		||||
 | 
			
		||||
        if (type == nullptr || err != SL_RESULT_SUCCESS)
 | 
			
		||||
            *this = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
    T* const * type;
 | 
			
		||||
};
 | 
			
		||||
            if (type != nullptr && err == SL_RESULT_SUCCESS)
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct ContainerDeletePolicy<const SLObjectItf_* const>
 | 
			
		||||
{
 | 
			
		||||
    static void destroy (SLObjectItf object)
 | 
			
		||||
    {
 | 
			
		||||
        if (object != nullptr)
 | 
			
		||||
            (*object)->Destroy (object);
 | 
			
		||||
        *this = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    T* const* type = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
@ -281,8 +308,7 @@ public:
 | 
			
		||||
              numChannels (numChannelsToUse),
 | 
			
		||||
              nativeBuffer (static_cast<size_t> (numChannels * owner.bufferSize * owner.numBuffers)),
 | 
			
		||||
              scratchBuffer (numChannelsToUse, owner.bufferSize),
 | 
			
		||||
              sampleBuffer (scratchBuffer.getArrayOfWritePointers(), numChannelsToUse, owner.bufferSize),
 | 
			
		||||
              nextBlock (0), numBlocksOut (0)
 | 
			
		||||
              sampleBuffer (scratchBuffer.getArrayOfWritePointers(), numChannelsToUse, owner.bufferSize)
 | 
			
		||||
        {}
 | 
			
		||||
 | 
			
		||||
        ~OpenSLQueueRunner()
 | 
			
		||||
@ -297,10 +323,11 @@ public:
 | 
			
		||||
        bool init()
 | 
			
		||||
        {
 | 
			
		||||
            runner = crtp().createPlayerOrRecorder();
 | 
			
		||||
 | 
			
		||||
            if (runner == nullptr)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            const bool supportsJavaProxy = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24);
 | 
			
		||||
            const bool supportsJavaProxy = (getAndroidSDKVersion() >= 24);
 | 
			
		||||
 | 
			
		||||
            if (supportsJavaProxy)
 | 
			
		||||
            {
 | 
			
		||||
@ -314,18 +341,18 @@ public:
 | 
			
		||||
                                                               &audioRoutingJni);
 | 
			
		||||
 | 
			
		||||
                    if (status == SL_RESULT_SUCCESS && audioRoutingJni != 0)
 | 
			
		||||
                        javaProxy = GlobalRef (audioRoutingJni);
 | 
			
		||||
                        javaProxy = GlobalRef (LocalRef<jobject>(getEnv()->NewLocalRef (audioRoutingJni)));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            queue = SlRef<SLAndroidSimpleBufferQueueItf_>::cast (runner);
 | 
			
		||||
 | 
			
		||||
            if (queue == nullptr)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            return ((*queue)->RegisterCallback (queue, staticFinished, this) == SL_RESULT_SUCCESS);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        void clear()
 | 
			
		||||
        {
 | 
			
		||||
            nextBlock.set (0);
 | 
			
		||||
@ -349,8 +376,6 @@ public:
 | 
			
		||||
 | 
			
		||||
        void finished (SLAndroidSimpleBufferQueueItf)
 | 
			
		||||
        {
 | 
			
		||||
            attachAndroidJNI();
 | 
			
		||||
 | 
			
		||||
            --numBlocksOut;
 | 
			
		||||
            owner.doSomeWorkOnAudioThread();
 | 
			
		||||
        }
 | 
			
		||||
@ -376,7 +401,7 @@ public:
 | 
			
		||||
        HeapBlock<T> nativeBuffer;
 | 
			
		||||
        AudioBuffer<float> scratchBuffer, sampleBuffer;
 | 
			
		||||
 | 
			
		||||
        Atomic<int> nextBlock, numBlocksOut;
 | 
			
		||||
        Atomic<int> nextBlock { 0 }, numBlocksOut { 0 };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
@ -385,35 +410,36 @@ public:
 | 
			
		||||
    {
 | 
			
		||||
        using Base = OpenSLQueueRunner<T, OpenSLQueueRunnerPlayer<T>, SLPlayItf_>;
 | 
			
		||||
 | 
			
		||||
        enum { isPlayer = 1 };
 | 
			
		||||
 | 
			
		||||
        OpenSLQueueRunnerPlayer (OpenSLSessionT<T>& sessionToUse, int numChannelsToUse)
 | 
			
		||||
            : Base (sessionToUse, numChannelsToUse)
 | 
			
		||||
        {}
 | 
			
		||||
 | 
			
		||||
        SlRef<SLPlayItf_> createPlayerOrRecorder()
 | 
			
		||||
        {
 | 
			
		||||
            SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast<SLuint32> (Base::owner.numBuffers)};
 | 
			
		||||
            SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, Base::owner.outputMix};
 | 
			
		||||
            SLDataLocator_AndroidSimpleBufferQueue queueLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast<SLuint32> (Base::owner.numBuffers) };
 | 
			
		||||
            SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, Base::owner.outputMix };
 | 
			
		||||
 | 
			
		||||
            PCMDataFormatEx dataFormat;
 | 
			
		||||
            BufferHelpers<T>::initPCMDataFormat (dataFormat, Base::numChannels, Base::owner.sampleRate);
 | 
			
		||||
 | 
			
		||||
            SLDataSource source = {&queueLocator, &dataFormat};
 | 
			
		||||
            SLDataSink   sink   = {&outputMix,    nullptr};
 | 
			
		||||
            SLDataSource source = { &queueLocator, &dataFormat };
 | 
			
		||||
            SLDataSink   sink   = { &outputMix,    nullptr };
 | 
			
		||||
 | 
			
		||||
            SLInterfaceID queueInterfaces[] = { &IntfIID<SLAndroidSimpleBufferQueueItf_>::iid, &IntfIID<SLAndroidConfigurationItf_>::iid };
 | 
			
		||||
            SLboolean interfaceRequired[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE};
 | 
			
		||||
 | 
			
		||||
            SLObjectItf obj = nullptr;
 | 
			
		||||
 | 
			
		||||
            SLresult status = (*Base::owner.engine)->CreateAudioPlayer (Base::owner.engine, &obj, &source, &sink, 2, queueInterfaces, interfaceRequired);
 | 
			
		||||
            if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
            if (auto e = *Base::owner.engine)
 | 
			
		||||
            {
 | 
			
		||||
                if (obj != nullptr)
 | 
			
		||||
                    (*obj)->Destroy (obj);
 | 
			
		||||
                auto status = e->CreateAudioPlayer (Base::owner.engine, &obj, &source, &sink, 2,
 | 
			
		||||
                                                    queueInterfaces, interfaceRequired);
 | 
			
		||||
 | 
			
		||||
                return SlRef<SLPlayItf_>();
 | 
			
		||||
                if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize(obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
                {
 | 
			
		||||
                    destroyObject (obj);
 | 
			
		||||
                    return {};
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return SlRef<SLPlayItf_>::cast (SlObjectRef (obj));
 | 
			
		||||
@ -423,58 +449,56 @@ public:
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    struct OpenSLQueueRunnerRecorder      : OpenSLQueueRunner<T, OpenSLQueueRunnerRecorder<T>, SLRecordItf_>
 | 
			
		||||
    struct OpenSLQueueRunnerRecorder  : public OpenSLQueueRunner<T, OpenSLQueueRunnerRecorder<T>, SLRecordItf_>
 | 
			
		||||
    {
 | 
			
		||||
        using Base = OpenSLQueueRunner<T, OpenSLQueueRunnerRecorder<T>, SLRecordItf_>;
 | 
			
		||||
 | 
			
		||||
        enum { isPlayer = 0 };
 | 
			
		||||
 | 
			
		||||
        OpenSLQueueRunnerRecorder (OpenSLSessionT<T>& sessionToUse, int numChannelsToUse)
 | 
			
		||||
            : Base (sessionToUse, numChannelsToUse)
 | 
			
		||||
        {}
 | 
			
		||||
 | 
			
		||||
        SlRef<SLRecordItf_> createPlayerOrRecorder()
 | 
			
		||||
        {
 | 
			
		||||
            SLDataLocator_IODevice ioDeviceLocator = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr};
 | 
			
		||||
            SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast<SLuint32> (Base::owner.numBuffers)};
 | 
			
		||||
            SLDataLocator_IODevice ioDeviceLocator = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr };
 | 
			
		||||
            SLDataLocator_AndroidSimpleBufferQueue queueLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast<SLuint32> (Base::owner.numBuffers) };
 | 
			
		||||
 | 
			
		||||
            PCMDataFormatEx dataFormat;
 | 
			
		||||
            BufferHelpers<T>::initPCMDataFormat (dataFormat, Base::numChannels, Base::owner.sampleRate);
 | 
			
		||||
 | 
			
		||||
            SLDataSource source = {&ioDeviceLocator, nullptr};
 | 
			
		||||
            SLDataSink   sink   = {&queueLocator,    &dataFormat};
 | 
			
		||||
            SLDataSource source = { &ioDeviceLocator, nullptr };
 | 
			
		||||
            SLDataSink   sink   = { &queueLocator,    &dataFormat };
 | 
			
		||||
 | 
			
		||||
            SLInterfaceID queueInterfaces[] = { &IntfIID<SLAndroidSimpleBufferQueueItf_>::iid, &IntfIID<SLAndroidConfigurationItf_>::iid };
 | 
			
		||||
            SLboolean interfaceRequired[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE};
 | 
			
		||||
            SLboolean interfaceRequired[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
 | 
			
		||||
 | 
			
		||||
            SLObjectItf obj = nullptr;
 | 
			
		||||
 | 
			
		||||
            SLresult status = (*Base::owner.engine)->CreateAudioRecorder (Base::owner.engine, &obj, &source, &sink, 2, queueInterfaces, interfaceRequired);
 | 
			
		||||
            if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
            if (auto e = *Base::owner.engine)
 | 
			
		||||
            {
 | 
			
		||||
                if (obj != nullptr)
 | 
			
		||||
                    (*obj)->Destroy (obj);
 | 
			
		||||
                auto status = e->CreateAudioRecorder (Base::owner.engine, &obj, &source, &sink, 2, queueInterfaces, interfaceRequired);
 | 
			
		||||
 | 
			
		||||
                return SlRef<SLRecordItf_>();
 | 
			
		||||
                if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
                {
 | 
			
		||||
                    destroyObject (obj);
 | 
			
		||||
                    return {};
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SlRef<SLRecordItf_> recorder = SlRef<SLRecordItf_>::cast (SlObjectRef (obj));
 | 
			
		||||
 | 
			
		||||
            return recorder;
 | 
			
		||||
            return SlRef<SLRecordItf_>::cast (SlObjectRef (obj));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool setAudioPreprocessingEnabled (bool shouldEnable)
 | 
			
		||||
        {
 | 
			
		||||
            if (Base::config != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                const bool supportsUnprocessed = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 25);
 | 
			
		||||
                const bool supportsUnprocessed = (getAndroidSDKVersion() >= 25);
 | 
			
		||||
                const SLuint32 recordingPresetValue
 | 
			
		||||
                    = (shouldEnable ? SL_ANDROID_RECORDING_PRESET_GENERIC
 | 
			
		||||
                                    : (supportsUnprocessed ? SL_ANDROID_RECORDING_PRESET_UNPROCESSED
 | 
			
		||||
                                                           : SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION));
 | 
			
		||||
 | 
			
		||||
                SLresult status = (*Base::config)->SetConfiguration (Base::config, SL_ANDROID_KEY_RECORDING_PRESET,
 | 
			
		||||
                                                                     &recordingPresetValue, sizeof (recordingPresetValue));
 | 
			
		||||
                auto status = (*Base::config)->SetConfiguration (Base::config, SL_ANDROID_KEY_RECORDING_PRESET,
 | 
			
		||||
                                                                 &recordingPresetValue, sizeof (recordingPresetValue));
 | 
			
		||||
 | 
			
		||||
                return (status == SL_RESULT_SUCCESS);
 | 
			
		||||
            }
 | 
			
		||||
@ -482,7 +506,8 @@ public:
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void setState (bool running)    { (*Base::runner)->SetRecordState (Base::runner, running ? SL_RECORDSTATE_RECORDING : SL_RECORDSTATE_STOPPED); }
 | 
			
		||||
        void setState (bool running)    { (*Base::runner)->SetRecordState (Base::runner, running ? SL_RECORDSTATE_RECORDING
 | 
			
		||||
                                                                                                 : SL_RECORDSTATE_STOPPED); }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
@ -494,21 +519,19 @@ public:
 | 
			
		||||
                       double samleRateToUse, int bufferSizeToUse,
 | 
			
		||||
                       int numBuffersToUse)
 | 
			
		||||
            : inputChannels (numInputChannels), outputChannels (numOutputChannels),
 | 
			
		||||
              sampleRate (samleRateToUse), bufferSize (bufferSizeToUse), numBuffers (numBuffersToUse),
 | 
			
		||||
              running (false), audioProcessingEnabled (true), callback (nullptr)
 | 
			
		||||
              sampleRate (samleRateToUse), bufferSize (bufferSizeToUse), numBuffers (numBuffersToUse)
 | 
			
		||||
        {
 | 
			
		||||
            jassert (numInputChannels > 0 || numOutputChannels > 0);
 | 
			
		||||
 | 
			
		||||
            if (CreateEngineFunc createEngine = (CreateEngineFunc) slLibraryToUse.getFunction ("slCreateEngine"))
 | 
			
		||||
            if (auto createEngine = (CreateEngineFunc) slLibraryToUse.getFunction ("slCreateEngine"))
 | 
			
		||||
            {
 | 
			
		||||
                SLObjectItf obj = nullptr;
 | 
			
		||||
                auto err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr);
 | 
			
		||||
 | 
			
		||||
                SLresult err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr);
 | 
			
		||||
                if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
                if (err != SL_RESULT_SUCCESS || obj == nullptr || *obj == nullptr
 | 
			
		||||
                     || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
                {
 | 
			
		||||
                    if (obj != nullptr)
 | 
			
		||||
                        (*obj)->Destroy (obj);
 | 
			
		||||
 | 
			
		||||
                    destroyObject (obj);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -518,13 +541,12 @@ public:
 | 
			
		||||
            if (outputChannels > 0)
 | 
			
		||||
            {
 | 
			
		||||
                SLObjectItf obj = nullptr;
 | 
			
		||||
                auto err = (*engine)->CreateOutputMix (engine, &obj, 0, nullptr, nullptr);
 | 
			
		||||
 | 
			
		||||
                SLresult err = (*engine)->CreateOutputMix (engine, &obj, 0, nullptr, nullptr);
 | 
			
		||||
                if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
                if (err != SL_RESULT_SUCCESS || obj == nullptr || *obj == nullptr
 | 
			
		||||
                     || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
                {
 | 
			
		||||
                    if (obj != nullptr)
 | 
			
		||||
                        (*obj)->Destroy (obj);
 | 
			
		||||
 | 
			
		||||
                    destroyObject (obj);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -537,6 +559,7 @@ public:
 | 
			
		||||
        virtual bool openedOK() const    { return (engine != nullptr && (outputChannels == 0 || (outputMix != nullptr))); }
 | 
			
		||||
        virtual void start()             { stop(); jassert (callback.get() != nullptr); running = true; }
 | 
			
		||||
        virtual void stop()              { running = false; }
 | 
			
		||||
 | 
			
		||||
        virtual bool setAudioPreprocessingEnabled (bool shouldEnable) = 0;
 | 
			
		||||
        virtual bool supportsFloatingPoint() const noexcept = 0;
 | 
			
		||||
        virtual int getXRunCount() const noexcept = 0;
 | 
			
		||||
@ -553,9 +576,10 @@ public:
 | 
			
		||||
            jassert (callbackToUse != nullptr);
 | 
			
		||||
 | 
			
		||||
            // spin-lock until we can set the callback
 | 
			
		||||
            while (true)
 | 
			
		||||
            for (;;)
 | 
			
		||||
            {
 | 
			
		||||
                AudioIODeviceCallback* old = callback.get();
 | 
			
		||||
                auto old = callback.get();
 | 
			
		||||
 | 
			
		||||
                if (old == callbackToUse)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
@ -568,7 +592,7 @@ public:
 | 
			
		||||
 | 
			
		||||
        void process (const float** inputChannelData, float** outputChannelData)
 | 
			
		||||
        {
 | 
			
		||||
            if (AudioIODeviceCallback* cb = callback.exchange(nullptr))
 | 
			
		||||
            if (auto* cb = callback.exchange (nullptr))
 | 
			
		||||
            {
 | 
			
		||||
                cb->audioDeviceIOCallback (inputChannelData, inputChannels, outputChannelData, outputChannels, bufferSize);
 | 
			
		||||
                callback.set (cb);
 | 
			
		||||
@ -586,19 +610,19 @@ public:
 | 
			
		||||
                                      int numBuffersToUse);
 | 
			
		||||
 | 
			
		||||
        //==============================================================================
 | 
			
		||||
        typedef SLresult (*CreateEngineFunc)(SLObjectItf*, SLuint32, const SLEngineOption*, SLuint32, const SLInterfaceID*, const SLboolean*);
 | 
			
		||||
        using CreateEngineFunc = SLresult (*) (SLObjectItf*, SLuint32, const SLEngineOption*,
 | 
			
		||||
                                               SLuint32, const SLInterfaceID*, const SLboolean*);
 | 
			
		||||
 | 
			
		||||
        //==============================================================================
 | 
			
		||||
        int inputChannels, outputChannels;
 | 
			
		||||
        double sampleRate;
 | 
			
		||||
        int bufferSize, numBuffers;
 | 
			
		||||
 | 
			
		||||
        bool running, audioProcessingEnabled;
 | 
			
		||||
        bool running = false, audioProcessingEnabled = true;
 | 
			
		||||
 | 
			
		||||
        SlRef<SLEngineItf_> engine;
 | 
			
		||||
        SlRef<SLOutputMixItf_> outputMix;
 | 
			
		||||
 | 
			
		||||
        Atomic<AudioIODeviceCallback*> callback;
 | 
			
		||||
        Atomic<AudioIODeviceCallback*> callback { nullptr };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
@ -609,7 +633,8 @@ public:
 | 
			
		||||
                        int numInputChannels, int numOutputChannels,
 | 
			
		||||
                        double samleRateToUse, int bufferSizeToUse,
 | 
			
		||||
                        int numBuffersToUse)
 | 
			
		||||
            : OpenSLSession (slLibraryToUse, numInputChannels, numOutputChannels, samleRateToUse, bufferSizeToUse, numBuffersToUse)
 | 
			
		||||
            : OpenSLSession (slLibraryToUse, numInputChannels, numOutputChannels,
 | 
			
		||||
                             samleRateToUse, bufferSizeToUse, numBuffersToUse)
 | 
			
		||||
        {
 | 
			
		||||
            jassert (numInputChannels > 0 || numOutputChannels > 0);
 | 
			
		||||
 | 
			
		||||
@ -636,7 +661,7 @@ public:
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    const bool supportsUnderrunCount = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24);
 | 
			
		||||
                    const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24);
 | 
			
		||||
                    getUnderrunCount = supportsUnderrunCount ? getEnv()->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -644,8 +669,8 @@ public:
 | 
			
		||||
 | 
			
		||||
        bool openedOK() const override
 | 
			
		||||
        {
 | 
			
		||||
            return (OpenSLSession::openedOK() && (inputChannels == 0  || recorder != nullptr)
 | 
			
		||||
                                              && (outputChannels == 0 || player   != nullptr));
 | 
			
		||||
            return OpenSLSession::openedOK() && (inputChannels == 0  || recorder != nullptr)
 | 
			
		||||
                                             && (outputChannels == 0 || player   != nullptr);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void start() override
 | 
			
		||||
@ -762,11 +787,7 @@ public:
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    OpenSLAudioIODevice (const String& deviceName)
 | 
			
		||||
        : AudioIODevice (deviceName, openSLTypeName),
 | 
			
		||||
          actualBufferSize (0), sampleRate (0), audioBuffersToEnqueue (0),
 | 
			
		||||
          audioProcessingEnabled (true),
 | 
			
		||||
          callback (nullptr)
 | 
			
		||||
    OpenSLAudioIODevice (const String& deviceName)  : AudioIODevice (deviceName, openSLTypeName)
 | 
			
		||||
    {
 | 
			
		||||
        // OpenSL has piss-poor support for determining latency, so the only way I can find to
 | 
			
		||||
        // get a number for this is by asking the AudioTrack/AudioRecord classes..
 | 
			
		||||
@ -813,7 +834,7 @@ public:
 | 
			
		||||
 | 
			
		||||
    Array<double> getAvailableSampleRates() override
 | 
			
		||||
    {
 | 
			
		||||
        //see https://developer.android.com/ndk/guides/audio/opensl-for-android.html
 | 
			
		||||
        // see https://developer.android.com/ndk/guides/audio/opensl-for-android.html
 | 
			
		||||
 | 
			
		||||
        static const double rates[] = { 8000.0, 11025.0, 12000.0, 16000.0,
 | 
			
		||||
                                        22050.0, 24000.0, 32000.0, 44100.0, 48000.0 };
 | 
			
		||||
@ -821,6 +842,7 @@ public:
 | 
			
		||||
 | 
			
		||||
        // make sure the native sample rate is pafrt of the list
 | 
			
		||||
        double native = getNativeSampleRate();
 | 
			
		||||
 | 
			
		||||
        if (native != 0.0 && ! retval.contains (native))
 | 
			
		||||
            retval.add (native);
 | 
			
		||||
 | 
			
		||||
@ -850,7 +872,8 @@ public:
 | 
			
		||||
        close();
 | 
			
		||||
 | 
			
		||||
        lastError.clear();
 | 
			
		||||
        sampleRate = (int) requestedSampleRate;
 | 
			
		||||
 | 
			
		||||
        sampleRate = (int) (requestedSampleRate > 0 ? requestedSampleRate : getNativeSampleRate());
 | 
			
		||||
 | 
			
		||||
        auto totalPreferredBufferSize    = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
 | 
			
		||||
        auto nativeBufferSize            = getNativeBufferSize();
 | 
			
		||||
@ -862,11 +885,11 @@ public:
 | 
			
		||||
 | 
			
		||||
        activeOutputChans = outputChannels;
 | 
			
		||||
        activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
 | 
			
		||||
        int numOutputChannels = activeOutputChans.countNumberOfSetBits();
 | 
			
		||||
        auto numOutputChannels = activeOutputChans.countNumberOfSetBits();
 | 
			
		||||
 | 
			
		||||
        activeInputChans = inputChannels;
 | 
			
		||||
        activeInputChans.setRange (1, activeInputChans.getHighestBit(), false);
 | 
			
		||||
        int numInputChannels = activeInputChans.countNumberOfSetBits();
 | 
			
		||||
        auto numInputChannels = activeInputChans.countNumberOfSetBits();
 | 
			
		||||
 | 
			
		||||
        if (numInputChannels > 0 && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)))
 | 
			
		||||
        {
 | 
			
		||||
@ -879,7 +902,9 @@ public:
 | 
			
		||||
        session.reset (OpenSLSession::create (slLibrary, numInputChannels, numOutputChannels,
 | 
			
		||||
                                              sampleRate, actualBufferSize, audioBuffersToEnqueue));
 | 
			
		||||
        if (session != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            session->setAudioPreprocessingEnabled (audioProcessingEnabled);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (numInputChannels > 0 && numOutputChannels > 0 && RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
 | 
			
		||||
@ -945,7 +970,7 @@ public:
 | 
			
		||||
    {
 | 
			
		||||
        if (session != nullptr && callback != newCallback)
 | 
			
		||||
        {
 | 
			
		||||
            AudioIODeviceCallback* oldCallback = callback;
 | 
			
		||||
            auto oldCallback = callback;
 | 
			
		||||
 | 
			
		||||
            if (newCallback != nullptr)
 | 
			
		||||
                newCallback->audioDeviceAboutToStart (this);
 | 
			
		||||
@ -1001,12 +1026,12 @@ private:
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    DynamicLibrary slLibrary;
 | 
			
		||||
    int actualBufferSize, sampleRate, audioBuffersToEnqueue;
 | 
			
		||||
    int actualBufferSize = 0, sampleRate = 0, audioBuffersToEnqueue = 0;
 | 
			
		||||
    int inputLatency, outputLatency;
 | 
			
		||||
    bool deviceOpen, audioProcessingEnabled;
 | 
			
		||||
    bool deviceOpen = false, audioProcessingEnabled = true;
 | 
			
		||||
    String lastError;
 | 
			
		||||
    BigInteger activeOutputChans, activeInputChans;
 | 
			
		||||
    AudioIODeviceCallback* callback;
 | 
			
		||||
    AudioIODeviceCallback* callback = nullptr;
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<OpenSLSession> session;
 | 
			
		||||
 | 
			
		||||
@ -1024,9 +1049,7 @@ private:
 | 
			
		||||
            // "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required
 | 
			
		||||
            //  for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one
 | 
			
		||||
            //  is sufficient for lower latency."
 | 
			
		||||
 | 
			
		||||
            auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT);
 | 
			
		||||
            return (sdkVersion >= 18 ? 1 : 2);
 | 
			
		||||
            return (getAndroidSDKVersion() >= 18 ? 1 : 2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // we will not use the low-latency path so we can use the absolute minimum number of buffers
 | 
			
		||||
@ -1055,23 +1078,6 @@ private:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static String audioManagerGetProperty (const String& property)
 | 
			
		||||
    {
 | 
			
		||||
        const LocalRef<jstring> jProperty (javaString (property));
 | 
			
		||||
        const LocalRef<jstring> text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty,
 | 
			
		||||
                                                                                   jProperty.get()));
 | 
			
		||||
        if (text.get() != 0)
 | 
			
		||||
            return juceString (text);
 | 
			
		||||
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static bool androidHasSystemFeature (const String& property)
 | 
			
		||||
    {
 | 
			
		||||
        const LocalRef<jstring> jProperty (javaString (property));
 | 
			
		||||
        return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static double getNativeSampleRate()
 | 
			
		||||
    {
 | 
			
		||||
        return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue();
 | 
			
		||||
@ -1123,7 +1129,7 @@ OpenSLAudioIODevice::OpenSLSession* OpenSLAudioIODevice::OpenSLSession::create (
 | 
			
		||||
                                                                                int numBuffersToUse)
 | 
			
		||||
{
 | 
			
		||||
    std::unique_ptr<OpenSLSession> retval;
 | 
			
		||||
    auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT);
 | 
			
		||||
    auto sdkVersion = getAndroidSDKVersion();
 | 
			
		||||
 | 
			
		||||
    // SDK versions 21 and higher should natively support floating point...
 | 
			
		||||
    if (sdkVersion >= 21)
 | 
			
		||||
@ -1210,12 +1216,12 @@ public:
 | 
			
		||||
            SLObjectItf obj = nullptr;
 | 
			
		||||
            auto err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr);
 | 
			
		||||
 | 
			
		||||
            if (err != SL_RESULT_SUCCESS || obj == nullptr)
 | 
			
		||||
            if (err != SL_RESULT_SUCCESS || obj == nullptr || *obj == nullptr)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
            {
 | 
			
		||||
                (*obj)->Destroy (obj);
 | 
			
		||||
                destroyObject (obj);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1223,7 +1229,7 @@ public:
 | 
			
		||||
 | 
			
		||||
            if (engine == nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                (*obj)->Destroy (obj);
 | 
			
		||||
                destroyObject (obj);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1232,7 +1238,7 @@ public:
 | 
			
		||||
 | 
			
		||||
            if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
            {
 | 
			
		||||
                (*obj)->Destroy (obj);
 | 
			
		||||
                destroyObject (obj);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1240,7 +1246,7 @@ public:
 | 
			
		||||
 | 
			
		||||
            if (outputMix == nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                (*obj)->Destroy (obj);
 | 
			
		||||
                destroyObject (obj);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1264,7 +1270,7 @@ public:
 | 
			
		||||
 | 
			
		||||
            if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS)
 | 
			
		||||
            {
 | 
			
		||||
                (*obj)->Destroy (obj);
 | 
			
		||||
                destroyObject (obj);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1272,7 +1278,7 @@ public:
 | 
			
		||||
 | 
			
		||||
            if (player == nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                (*obj)->Destroy (obj);
 | 
			
		||||
                destroyObject (obj);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -235,6 +235,7 @@ private:
 | 
			
		||||
 | 
			
		||||
    SharedResourcePointer<AudioSessionHolder> sessionHolder;
 | 
			
		||||
 | 
			
		||||
    JUCE_DECLARE_WEAK_REFERENCEABLE (iOSAudioIODeviceType)
 | 
			
		||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -242,7 +243,7 @@ private:
 | 
			
		||||
struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
                                      public AsyncUpdater
 | 
			
		||||
{
 | 
			
		||||
    Pimpl (iOSAudioIODeviceType& ioDeviceType, iOSAudioIODevice& ioDevice)
 | 
			
		||||
    Pimpl (iOSAudioIODeviceType* ioDeviceType, iOSAudioIODevice& ioDevice)
 | 
			
		||||
        : deviceType (ioDeviceType),
 | 
			
		||||
          owner (ioDevice)
 | 
			
		||||
    {
 | 
			
		||||
@ -255,9 +256,8 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
 | 
			
		||||
        setAudioSessionActive (true);
 | 
			
		||||
        updateHardwareInfo();
 | 
			
		||||
        setAudioSessionActive (false);
 | 
			
		||||
 | 
			
		||||
        channelData.reconfigure ({}, {});
 | 
			
		||||
        setAudioSessionActive (false);
 | 
			
		||||
 | 
			
		||||
        sessionHolder->activeDevices.add (this);
 | 
			
		||||
    }
 | 
			
		||||
@ -278,7 +278,9 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
       #endif
 | 
			
		||||
 | 
			
		||||
        if (category == AVAudioSessionCategoryPlayAndRecord)
 | 
			
		||||
            options |= (AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth);
 | 
			
		||||
            options |= (AVAudioSessionCategoryOptionDefaultToSpeaker
 | 
			
		||||
                      | AVAudioSessionCategoryOptionAllowBluetooth
 | 
			
		||||
                      | AVAudioSessionCategoryOptionAllowBluetoothA2DP);
 | 
			
		||||
 | 
			
		||||
        JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setCategory: category
 | 
			
		||||
                                                             withOptions: options
 | 
			
		||||
@ -405,9 +407,9 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
        JUCE_IOS_AUDIO_LOG ("Sample rate after detecting available sample rates: " << sampleRate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void updateHardwareInfo()
 | 
			
		||||
    void updateHardwareInfo (bool forceUpdate = false)
 | 
			
		||||
    {
 | 
			
		||||
        if (! hardwareInfoNeedsUpdating.compareAndSetBool (false, true))
 | 
			
		||||
        if (! forceUpdate && ! hardwareInfoNeedsUpdating.compareAndSetBool (false, true))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        JUCE_IOS_AUDIO_LOG ("Updating hardware info");
 | 
			
		||||
@ -415,7 +417,8 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
        updateAvailableSampleRates();
 | 
			
		||||
        updateAvailableBufferSizes();
 | 
			
		||||
 | 
			
		||||
        deviceType.callDeviceChangeListeners();
 | 
			
		||||
        if (deviceType != nullptr)
 | 
			
		||||
            deviceType->callDeviceChangeListeners();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void setTargetSampleRateAndBufferSize()
 | 
			
		||||
@ -451,13 +454,12 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
                            << ", targetSampleRate: " << targetSampleRate
 | 
			
		||||
                            << ", targetBufferSize: " << targetBufferSize);
 | 
			
		||||
 | 
			
		||||
        channelData.reconfigure (requestedInputChannels, requestedOutputChannels);
 | 
			
		||||
 | 
			
		||||
        setAudioSessionCategory (channelData.areInputChannelsAvailable() ? AVAudioSessionCategoryPlayAndRecord : AVAudioSessionCategoryPlayback);
 | 
			
		||||
        setAudioSessionActive (true);
 | 
			
		||||
 | 
			
		||||
        setAudioSessionCategory (requestedInputChannels > 0 ? AVAudioSessionCategoryPlayAndRecord
 | 
			
		||||
                                                            : AVAudioSessionCategoryPlayback);
 | 
			
		||||
        channelData.reconfigure (requestedInputChannels, requestedOutputChannels);
 | 
			
		||||
        updateHardwareInfo (true);
 | 
			
		||||
        setTargetSampleRateAndBufferSize();
 | 
			
		||||
 | 
			
		||||
        fixAudioRouteIfSetToReceiver();
 | 
			
		||||
 | 
			
		||||
        isRunning = true;
 | 
			
		||||
@ -486,11 +488,11 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
        {
 | 
			
		||||
            isRunning = false;
 | 
			
		||||
 | 
			
		||||
            if (audioUnit != 0)
 | 
			
		||||
            if (audioUnit != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                AudioOutputUnitStart (audioUnit);
 | 
			
		||||
                AudioComponentInstanceDispose (audioUnit);
 | 
			
		||||
                audioUnit = 0;
 | 
			
		||||
                audioUnit = nullptr;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setAudioSessionActive (false);
 | 
			
		||||
@ -819,6 +821,9 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
            {
 | 
			
		||||
                AudioOutputUnitStop (audioUnit);
 | 
			
		||||
                setAudioSessionActive (false);
 | 
			
		||||
 | 
			
		||||
                if (callback != nullptr)
 | 
			
		||||
                    callback->audioDeviceStopped();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -836,9 +841,7 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
        if (useInput)
 | 
			
		||||
            err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
 | 
			
		||||
 | 
			
		||||
        const int numChannels = jmax (channelData.inputs->numHardwareChannels,
 | 
			
		||||
                                      channelData.outputs->numHardwareChannels);
 | 
			
		||||
        const UInt32 totalDataSize = sizeof (short) * (uint32) numChannels * numFrames;
 | 
			
		||||
        const auto channelDataSize = sizeof (float) * numFrames;
 | 
			
		||||
 | 
			
		||||
        const ScopedTryLock stl (callbackLock);
 | 
			
		||||
 | 
			
		||||
@ -847,49 +850,40 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
            if ((int) numFrames > channelData.getFloatBufferSize())
 | 
			
		||||
                channelData.setFloatBufferSize ((int) numFrames);
 | 
			
		||||
 | 
			
		||||
            float** inputData  = channelData.audioData.getArrayOfWritePointers();
 | 
			
		||||
            float** outputData = inputData + channelData.inputs->numActiveChannels;
 | 
			
		||||
            float** const inputData = channelData.audioData.getArrayOfWritePointers();
 | 
			
		||||
            float** const outputData = inputData + channelData.inputs->numActiveChannels;
 | 
			
		||||
 | 
			
		||||
            if (useInput)
 | 
			
		||||
            {
 | 
			
		||||
                const auto* inputShortData = (short*) data->mBuffers[0].mData;
 | 
			
		||||
 | 
			
		||||
                for (UInt32 i = 0; i < numFrames; ++i)
 | 
			
		||||
                for (int c = 0; c < channelData.inputs->numActiveChannels; ++c)
 | 
			
		||||
                {
 | 
			
		||||
                    for (int channel = 0; channel < channelData.inputs->numActiveChannels; ++channel)
 | 
			
		||||
                        inputData[channel][i] = *(inputShortData + channelData.inputs->activeChannelIndicies[channel]) * (1.0f / 32768.0f);
 | 
			
		||||
 | 
			
		||||
                    inputShortData += numChannels;
 | 
			
		||||
                    auto channelIndex = channelData.inputs->activeChannelIndices[c];
 | 
			
		||||
                    memcpy (inputData[c], (float*) data->mBuffers[channelIndex].mData, channelDataSize);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                for (int i = 0; i < channelData.inputs->numActiveChannels; ++i)
 | 
			
		||||
                    zeromem (inputData, sizeof (float) * numFrames);
 | 
			
		||||
                for (int c = 0; c < channelData.inputs->numActiveChannels; ++c)
 | 
			
		||||
                    zeromem (inputData[c], channelDataSize);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            callback->audioDeviceIOCallback ((const float**) inputData,  channelData.inputs ->numActiveChannels,
 | 
			
		||||
                                                             outputData, channelData.outputs->numActiveChannels,
 | 
			
		||||
                                             (int) numFrames);
 | 
			
		||||
 | 
			
		||||
            auto* outputShortData = (short*) data->mBuffers[0].mData;
 | 
			
		||||
 | 
			
		||||
            zeromem (outputShortData, totalDataSize);
 | 
			
		||||
 | 
			
		||||
            if (channelData.outputs->numActiveChannels > 0)
 | 
			
		||||
            for (int c = 0; c < channelData.outputs->numActiveChannels; ++c)
 | 
			
		||||
            {
 | 
			
		||||
                for (UInt32 i = 0; i < numFrames; ++i)
 | 
			
		||||
                {
 | 
			
		||||
                    for (int channel = 0; channel < channelData.outputs->numActiveChannels; ++channel)
 | 
			
		||||
                        *(outputShortData + channelData.outputs->activeChannelIndicies[channel]) = (short) (outputData[channel][i] * 32767.0f);
 | 
			
		||||
 | 
			
		||||
                    outputShortData += numChannels;
 | 
			
		||||
                }
 | 
			
		||||
                auto channelIndex = channelData.outputs->activeChannelIndices[c];
 | 
			
		||||
                memcpy (data->mBuffers[channelIndex].mData, outputData[c], channelDataSize);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (auto c : channelData.outputs->inactiveChannelIndices)
 | 
			
		||||
                zeromem (data->mBuffers[c].mData, channelDataSize);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            zeromem (data->mBuffers[0].mData, totalDataSize);
 | 
			
		||||
            for (uint32 c = 0; c < data->mNumberBuffers; ++c)
 | 
			
		||||
                zeromem (data->mBuffers[c].mData, channelDataSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return err;
 | 
			
		||||
@ -927,10 +921,10 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
    {
 | 
			
		||||
        JUCE_IOS_AUDIO_LOG ("Creating the audio unit");
 | 
			
		||||
 | 
			
		||||
        if (audioUnit != 0)
 | 
			
		||||
        if (audioUnit != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            AudioComponentInstanceDispose (audioUnit);
 | 
			
		||||
            audioUnit = 0;
 | 
			
		||||
            audioUnit = nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AudioComponentDescription desc;
 | 
			
		||||
@ -940,10 +934,10 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
        desc.componentFlags = 0;
 | 
			
		||||
        desc.componentFlagsMask = 0;
 | 
			
		||||
 | 
			
		||||
        AudioComponent comp = AudioComponentFindNext (0, &desc);
 | 
			
		||||
        AudioComponent comp = AudioComponentFindNext (nullptr, &desc);
 | 
			
		||||
        AudioComponentInstanceNew (comp, &audioUnit);
 | 
			
		||||
 | 
			
		||||
        if (audioUnit == 0)
 | 
			
		||||
        if (audioUnit == nullptr)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
       #if JucePlugin_Enable_IAA
 | 
			
		||||
@ -1001,11 +995,11 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
            zerostruct (format);
 | 
			
		||||
            format.mSampleRate = sampleRate;
 | 
			
		||||
            format.mFormatID = kAudioFormatLinearPCM;
 | 
			
		||||
            format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
 | 
			
		||||
            format.mBitsPerChannel = 8 * sizeof (short);
 | 
			
		||||
            format.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
 | 
			
		||||
            format.mBitsPerChannel = 8 * sizeof (float);
 | 
			
		||||
            format.mFramesPerPacket = 1;
 | 
			
		||||
            format.mChannelsPerFrame = (UInt32) jmax (channelData.inputs->numHardwareChannels, channelData.outputs->numHardwareChannels);
 | 
			
		||||
            format.mBytesPerFrame = format.mBytesPerPacket = format.mChannelsPerFrame * sizeof (short);
 | 
			
		||||
            format.mBytesPerFrame = format.mBytesPerPacket = sizeof (float);
 | 
			
		||||
 | 
			
		||||
            AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,  0, &format, sizeof (format));
 | 
			
		||||
            AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
 | 
			
		||||
@ -1082,10 +1076,10 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
 | 
			
		||||
        if (isRunning)
 | 
			
		||||
        {
 | 
			
		||||
            if (audioUnit != 0)
 | 
			
		||||
            if (audioUnit != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                AudioComponentInstanceDispose (audioUnit);
 | 
			
		||||
                audioUnit = 0;
 | 
			
		||||
                audioUnit = nullptr;
 | 
			
		||||
 | 
			
		||||
                if (callback != nullptr)
 | 
			
		||||
                    callback->audioDeviceStopped();
 | 
			
		||||
@ -1095,7 +1089,7 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
 | 
			
		||||
            createAudioUnit();
 | 
			
		||||
 | 
			
		||||
            if (audioUnit != 0)
 | 
			
		||||
            if (audioUnit != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                isRunning = true;
 | 
			
		||||
 | 
			
		||||
@ -1172,7 +1166,8 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
                  areChannelsAccessible ((! isInput) || [AVAudioSession sharedInstance].isInputAvailable),
 | 
			
		||||
                  activeChannels (limitRequiredChannelsToHardware (numHardwareChannels, requiredChannels)),
 | 
			
		||||
                  numActiveChannels (activeChannels.countNumberOfSetBits()),
 | 
			
		||||
                  activeChannelIndicies (getActiveChannelIndicies (activeChannels))
 | 
			
		||||
                  activeChannelIndices (getActiveChannelIndices (activeChannels)),
 | 
			
		||||
                  inactiveChannelIndices (getInactiveChannelIndices (activeChannelIndices, numHardwareChannels))
 | 
			
		||||
            {
 | 
			
		||||
               #if JUCE_IOS_AUDIO_LOGGING
 | 
			
		||||
                {
 | 
			
		||||
@ -1187,7 +1182,12 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
                    info << ", Are channels available: " << (areChannelsAccessible ? "yes" : "no")
 | 
			
		||||
                         << ", Active channel indices:";
 | 
			
		||||
 | 
			
		||||
                    for (auto i : activeChannelIndicies)
 | 
			
		||||
                    for (auto i : activeChannelIndices)
 | 
			
		||||
                        info << " " << i;
 | 
			
		||||
 | 
			
		||||
                    info << ", Inactive channel indices:";
 | 
			
		||||
 | 
			
		||||
                    for (auto i : inactiveChannelIndices)
 | 
			
		||||
                        info << " " << i;
 | 
			
		||||
 | 
			
		||||
                    JUCE_IOS_AUDIO_LOG ((isInput ? "Input" : "Output") << " channel configuration: {" << info << "}");
 | 
			
		||||
@ -1202,7 +1202,7 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
            const BigInteger activeChannels;
 | 
			
		||||
            const int numActiveChannels;
 | 
			
		||||
 | 
			
		||||
            const Array<int> activeChannelIndicies;
 | 
			
		||||
            const Array<int> activeChannelIndices, inactiveChannelIndices;
 | 
			
		||||
 | 
			
		||||
        private:
 | 
			
		||||
            static StringArray getHardwareChannelNames (const bool isInput)
 | 
			
		||||
@ -1234,7 +1234,7 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
                return requiredChannels;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static Array<int> getActiveChannelIndicies (const BigInteger activeChannelsToIndex)
 | 
			
		||||
            static Array<int> getActiveChannelIndices (const BigInteger activeChannelsToIndex)
 | 
			
		||||
            {
 | 
			
		||||
                Array<int> result;
 | 
			
		||||
 | 
			
		||||
@ -1248,6 +1248,21 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static Array<int> getInactiveChannelIndices (const Array<int>& activeIndices, int numChannels)
 | 
			
		||||
            {
 | 
			
		||||
                Array<int> result;
 | 
			
		||||
 | 
			
		||||
                auto nextActiveChannel = activeIndices.begin();
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < numChannels; ++i)
 | 
			
		||||
                    if (nextActiveChannel != activeIndices.end() && i == *nextActiveChannel)
 | 
			
		||||
                        ++nextActiveChannel;
 | 
			
		||||
                    else
 | 
			
		||||
                        result.add (i);
 | 
			
		||||
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        void reconfigure (const BigInteger requiredInputChannels,
 | 
			
		||||
@ -1307,7 +1322,7 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
 | 
			
		||||
    MidiMessageCollector* messageCollector = nullptr;
 | 
			
		||||
 | 
			
		||||
    iOSAudioIODeviceType& deviceType;
 | 
			
		||||
    WeakReference<iOSAudioIODeviceType> deviceType;
 | 
			
		||||
    iOSAudioIODevice& owner;
 | 
			
		||||
 | 
			
		||||
    CriticalSection callbackLock;
 | 
			
		||||
@ -1327,7 +1342,7 @@ struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
iOSAudioIODevice::iOSAudioIODevice (iOSAudioIODeviceType& ioDeviceType, const String&, const String&)
 | 
			
		||||
iOSAudioIODevice::iOSAudioIODevice (iOSAudioIODeviceType* ioDeviceType, const String&, const String&)
 | 
			
		||||
    : AudioIODevice (iOSAudioDeviceName, iOSAudioDeviceName),
 | 
			
		||||
      pimpl (new Pimpl (ioDeviceType, *this))
 | 
			
		||||
{
 | 
			
		||||
@ -1401,7 +1416,7 @@ bool iOSAudioIODeviceType::hasSeparateInputsAndOutputs() const            { retu
 | 
			
		||||
 | 
			
		||||
AudioIODevice* iOSAudioIODeviceType::createDevice (const String& outputDeviceName, const String& inputDeviceName)
 | 
			
		||||
{
 | 
			
		||||
    return new iOSAudioIODevice (*this, outputDeviceName, inputDeviceName);
 | 
			
		||||
    return new iOSAudioIODevice (this, outputDeviceName, inputDeviceName);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void iOSAudioIODeviceType::handleRouteChange (AVAudioSessionRouteChangeReason)
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,7 @@ public:
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    iOSAudioIODevice (iOSAudioIODeviceType&, const String&, const String&);
 | 
			
		||||
    iOSAudioIODevice (iOSAudioIODeviceType*, const String&, const String&);
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    friend class iOSAudioIODeviceType;
 | 
			
		||||
 | 
			
		||||
@ -1115,7 +1115,7 @@ private:
 | 
			
		||||
            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_open (&handle, ("hw:" + String (cardNum)).toRawUTF8(), SND_CTL_NONBLOCK)) >= 0)
 | 
			
		||||
            {
 | 
			
		||||
                if (JUCE_CHECKED_RESULT (snd_ctl_card_info (handle, info)) >= 0)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,136 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class BelaMidiInput
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    static Array<BelaMidiInput*> midiInputs;
 | 
			
		||||
 | 
			
		||||
    BelaMidiInput (const String& port, MidiInput* input, MidiInputCallback* callback)
 | 
			
		||||
        : midiInput (input), midiPort (port), midiCallback (callback)
 | 
			
		||||
    {
 | 
			
		||||
        jassert (midiCallback != nullptr);
 | 
			
		||||
        midiInputs.add (this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~BelaMidiInput()
 | 
			
		||||
    {
 | 
			
		||||
        stop();
 | 
			
		||||
        midiInputs.removeAllInstancesOf (this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void start()
 | 
			
		||||
    {
 | 
			
		||||
        midi.readFrom (midiPort.toRawUTF8());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void stop()
 | 
			
		||||
    {
 | 
			
		||||
        midi.enableParser (false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void poll()
 | 
			
		||||
    {
 | 
			
		||||
        for (;;)
 | 
			
		||||
        {
 | 
			
		||||
            auto data = midi.getInput();
 | 
			
		||||
 | 
			
		||||
            if (data < 0)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            auto byte = (uint8) data;
 | 
			
		||||
            concatenator.pushMidiData (&byte, 1, 0.0, midiInput, *midiCallback);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static StringArray getDevices (bool input)
 | 
			
		||||
    {
 | 
			
		||||
        StringArray devices;
 | 
			
		||||
 | 
			
		||||
        for (auto& card : findAllALSACardIDs())
 | 
			
		||||
            findMidiDevices (devices, input, card);
 | 
			
		||||
 | 
			
		||||
        return devices;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    static Array<int> findAllALSACardIDs()
 | 
			
		||||
    {
 | 
			
		||||
        Array<int> cards;
 | 
			
		||||
        int card = -1;
 | 
			
		||||
 | 
			
		||||
        for (;;)
 | 
			
		||||
        {
 | 
			
		||||
            auto status = snd_card_next (&card);
 | 
			
		||||
 | 
			
		||||
            if (status != 0 || card < 0)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            cards.add (card);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return cards;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Adds all midi devices to the devices array of the given input/output type on the given card
 | 
			
		||||
    static void findMidiDevices (StringArray& devices, bool input, int cardNum)
 | 
			
		||||
    {
 | 
			
		||||
        snd_ctl_t* ctl = nullptr;
 | 
			
		||||
        auto status = snd_ctl_open (&ctl, ("hw:" + String (cardNum)).toRawUTF8(), 0);
 | 
			
		||||
 | 
			
		||||
        if (status < 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        int device = -1;
 | 
			
		||||
 | 
			
		||||
        for (;;)
 | 
			
		||||
        {
 | 
			
		||||
            status = snd_ctl_rawmidi_next_device (ctl, &device);
 | 
			
		||||
 | 
			
		||||
            if (status < 0 || device < 0)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            snd_rawmidi_info_t* info;
 | 
			
		||||
            snd_rawmidi_info_alloca (&info);
 | 
			
		||||
 | 
			
		||||
            snd_rawmidi_info_set_device (info, device);
 | 
			
		||||
            snd_rawmidi_info_set_stream (info, input ? SND_RAWMIDI_STREAM_INPUT
 | 
			
		||||
                                                     : SND_RAWMIDI_STREAM_OUTPUT);
 | 
			
		||||
 | 
			
		||||
            snd_ctl_rawmidi_info (ctl, info);
 | 
			
		||||
 | 
			
		||||
            auto subCount = snd_rawmidi_info_get_subdevices_count (info);
 | 
			
		||||
 | 
			
		||||
            for (int sub = 0; sub < subCount; ++sub)
 | 
			
		||||
            {
 | 
			
		||||
                snd_rawmidi_info_set_subdevice (info, sub);
 | 
			
		||||
 | 
			
		||||
                status = snd_ctl_rawmidi_info (ctl, info);
 | 
			
		||||
 | 
			
		||||
                if (status == 0)
 | 
			
		||||
                    devices.add ("hw:" + String (cardNum) + ","
 | 
			
		||||
                                       + String (device) + ","
 | 
			
		||||
                                       + String (sub));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        snd_ctl_close (ctl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String midiPort;
 | 
			
		||||
    MidiInput* const midiInput;
 | 
			
		||||
    MidiInputCallback* const midiCallback;
 | 
			
		||||
 | 
			
		||||
    Midi midi;
 | 
			
		||||
    MidiDataConcatenator concatenator { 512 };
 | 
			
		||||
 | 
			
		||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaMidiInput)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Array<BelaMidiInput*> BelaMidiInput::midiInputs;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class BelaAudioIODevice   : public AudioIODevice
 | 
			
		||||
{
 | 
			
		||||
@ -33,7 +163,10 @@ public:
 | 
			
		||||
        Bela_defaultSettings (&defaultSettings);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~BelaAudioIODevice() {}
 | 
			
		||||
    ~BelaAudioIODevice()
 | 
			
		||||
    {
 | 
			
		||||
        close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    StringArray getOutputChannelNames() override           { return { "Out #1", "Out #2" }; }
 | 
			
		||||
@ -223,6 +356,10 @@ private:
 | 
			
		||||
 | 
			
		||||
        ScopedLock lock (callbackLock);
 | 
			
		||||
 | 
			
		||||
        // Check for and process and midi
 | 
			
		||||
        for (auto midiInput : BelaMidiInput::midiInputs)
 | 
			
		||||
            midiInput->poll();
 | 
			
		||||
 | 
			
		||||
        if (callback != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            jassert (context.audioFrames <= actualBufferSize);
 | 
			
		||||
@ -370,4 +507,58 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela()
 | 
			
		||||
    return new BelaAudioIODeviceType();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// TODO: Add Bela MidiOutput support
 | 
			
		||||
 | 
			
		||||
StringArray MidiOutput::getDevices()                                { return {}; }
 | 
			
		||||
int MidiOutput::getDefaultDeviceIndex()                             { return 0; }
 | 
			
		||||
MidiOutput* MidiOutput::openDevice (int)                            { return {}; }
 | 
			
		||||
MidiOutput* MidiOutput::createNewDevice (const String&)             { return {}; }
 | 
			
		||||
MidiOutput::~MidiOutput() {}
 | 
			
		||||
void MidiOutput::sendMessageNow (const MidiMessage&) {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
MidiInput::MidiInput (const String& nm) : name (nm) {}
 | 
			
		||||
 | 
			
		||||
MidiInput::~MidiInput()
 | 
			
		||||
{
 | 
			
		||||
    delete static_cast<BelaMidiInput*> (internal);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MidiInput::start()     { static_cast<BelaMidiInput*> (internal)->start(); }
 | 
			
		||||
void MidiInput::stop()      { static_cast<BelaMidiInput*> (internal)->stop(); }
 | 
			
		||||
 | 
			
		||||
int MidiInput::getDefaultDeviceIndex()
 | 
			
		||||
{
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
StringArray MidiInput::getDevices()
 | 
			
		||||
{
 | 
			
		||||
    return BelaMidiInput::getDevices (true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
 | 
			
		||||
{
 | 
			
		||||
    auto devices = getDevices();
 | 
			
		||||
 | 
			
		||||
    if (index >= 0 && index < devices.size())
 | 
			
		||||
    {
 | 
			
		||||
        auto deviceName = devices[index];
 | 
			
		||||
        auto result = new MidiInput (deviceName);
 | 
			
		||||
        result->internal = new BelaMidiInput (deviceName, result, callback);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
 | 
			
		||||
{
 | 
			
		||||
    jassertfalse; // N/A on Bela
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace juce
 | 
			
		||||
 | 
			
		||||
@ -25,11 +25,6 @@ namespace juce
 | 
			
		||||
 | 
			
		||||
#if JUCE_ALSA
 | 
			
		||||
 | 
			
		||||
// You can define these strings in your app if you want to override the default names:
 | 
			
		||||
#ifndef JUCE_ALSA_MIDI_NAME
 | 
			
		||||
 #define JUCE_ALSA_MIDI_NAME  JUCEApplicationBase::getInstance()->getApplicationName().toUTF8()
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
namespace
 | 
			
		||||
{
 | 
			
		||||
@ -38,6 +33,49 @@ namespace
 | 
			
		||||
class AlsaClient  : public ReferenceCountedObject
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AlsaClient()
 | 
			
		||||
    {
 | 
			
		||||
        jassert (instance == nullptr);
 | 
			
		||||
 | 
			
		||||
        snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
 | 
			
		||||
 | 
			
		||||
        if (handle != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
 | 
			
		||||
            snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8());
 | 
			
		||||
            clientId = snd_seq_client_id (handle);
 | 
			
		||||
 | 
			
		||||
            // It's good idea to pre-allocate a good number of elements
 | 
			
		||||
            ports.ensureStorageAllocated (32);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~AlsaClient()
 | 
			
		||||
    {
 | 
			
		||||
        jassert (instance != nullptr);
 | 
			
		||||
        instance = nullptr;
 | 
			
		||||
 | 
			
		||||
        if (handle != nullptr)
 | 
			
		||||
            snd_seq_close (handle);
 | 
			
		||||
 | 
			
		||||
        jassert (activeCallbacks.get() == 0);
 | 
			
		||||
 | 
			
		||||
        if (inputThread)
 | 
			
		||||
            inputThread->stopThread (3000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static String getAlsaMidiName()
 | 
			
		||||
    {
 | 
			
		||||
        #ifdef JUCE_ALSA_MIDI_NAME
 | 
			
		||||
            return JUCE_ALSA_MIDI_NAME;
 | 
			
		||||
        #else
 | 
			
		||||
            if (auto* app = JUCEApplicationBase::getInstance())
 | 
			
		||||
                return app->getApplicationName();
 | 
			
		||||
 | 
			
		||||
            return "JUCE";
 | 
			
		||||
        #endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    using Ptr = ReferenceCountedObjectPtr<AlsaClient>;
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
@ -113,9 +151,9 @@ public:
 | 
			
		||||
            snd_seq_ev_clear (&event);
 | 
			
		||||
 | 
			
		||||
            auto numBytes = (long) message.getRawDataSize();
 | 
			
		||||
            const uint8* data = message.getRawData();
 | 
			
		||||
            auto* data = message.getRawData();
 | 
			
		||||
 | 
			
		||||
            auto* seqHandle = client.get();
 | 
			
		||||
            auto seqHandle = client.get();
 | 
			
		||||
            bool success = true;
 | 
			
		||||
 | 
			
		||||
            while (numBytes > 0)
 | 
			
		||||
@ -154,12 +192,12 @@ public:
 | 
			
		||||
 | 
			
		||||
        void createPort (const String& name, bool enableSubscription)
 | 
			
		||||
        {
 | 
			
		||||
            if (auto* seqHandle = client.get())
 | 
			
		||||
            if (auto seqHandle = client.get())
 | 
			
		||||
            {
 | 
			
		||||
                const unsigned int caps =
 | 
			
		||||
                    isInput
 | 
			
		||||
                    ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
 | 
			
		||||
                    : (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
 | 
			
		||||
                    isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
 | 
			
		||||
                            : (SND_SEQ_PORT_CAP_READ  | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
 | 
			
		||||
 | 
			
		||||
                portId = snd_seq_create_simple_port (seqHandle, name.toUTF8(), caps,
 | 
			
		||||
                                                     SND_SEQ_PORT_TYPE_MIDI_GENERIC |
 | 
			
		||||
                                                     SND_SEQ_PORT_TYPE_APPLICATION);
 | 
			
		||||
@ -251,38 +289,6 @@ private:
 | 
			
		||||
 | 
			
		||||
    static AlsaClient* instance;
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    friend class ReferenceCountedObjectPtr<AlsaClient>;
 | 
			
		||||
    friend struct ContainerDeletePolicy<AlsaClient>;
 | 
			
		||||
 | 
			
		||||
    AlsaClient()
 | 
			
		||||
    {
 | 
			
		||||
        jassert (instance == nullptr);
 | 
			
		||||
 | 
			
		||||
        snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
 | 
			
		||||
        snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
 | 
			
		||||
        snd_seq_set_client_name (handle, JUCE_ALSA_MIDI_NAME);
 | 
			
		||||
        clientId = snd_seq_client_id(handle);
 | 
			
		||||
 | 
			
		||||
        // It's good idea to pre-allocate a good number of elements
 | 
			
		||||
        ports.ensureStorageAllocated (32);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~AlsaClient()
 | 
			
		||||
    {
 | 
			
		||||
        jassert (instance != nullptr);
 | 
			
		||||
 | 
			
		||||
        instance = nullptr;
 | 
			
		||||
 | 
			
		||||
        if (handle != nullptr)
 | 
			
		||||
            snd_seq_close (handle);
 | 
			
		||||
 | 
			
		||||
        jassert (activeCallbacks.get() == 0);
 | 
			
		||||
 | 
			
		||||
        if (inputThread)
 | 
			
		||||
            inputThread->stopThread (3000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    class MidiInputThread   : public Thread
 | 
			
		||||
    {
 | 
			
		||||
@ -295,9 +301,10 @@ private:
 | 
			
		||||
 | 
			
		||||
        void run() override
 | 
			
		||||
        {
 | 
			
		||||
            auto seqHandle = client.get();
 | 
			
		||||
 | 
			
		||||
            const int maxEventSize = 16 * 1024;
 | 
			
		||||
            snd_midi_event_t* midiParser;
 | 
			
		||||
            snd_seq_t* seqHandle = client.get();
 | 
			
		||||
 | 
			
		||||
            if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
 | 
			
		||||
            {
 | 
			
		||||
@ -321,8 +328,8 @@ private:
 | 
			
		||||
                            if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                // xxx what about SYSEXes that are too big for the buffer?
 | 
			
		||||
                                const long numBytes = snd_midi_event_decode (midiParser, buffer,
 | 
			
		||||
                                                                            maxEventSize, inputEvent);
 | 
			
		||||
                                auto numBytes = snd_midi_event_decode (midiParser, buffer,
 | 
			
		||||
                                                                       maxEventSize, inputEvent);
 | 
			
		||||
 | 
			
		||||
                                snd_midi_event_reset_decode (midiParser);
 | 
			
		||||
 | 
			
		||||
@ -360,7 +367,7 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
 | 
			
		||||
{
 | 
			
		||||
    AlsaClient::Port* port = nullptr;
 | 
			
		||||
 | 
			
		||||
    snd_seq_t* seqHandle = client->get();
 | 
			
		||||
    auto seqHandle = client->get();
 | 
			
		||||
    snd_seq_port_info_t* portInfo = nullptr;
 | 
			
		||||
 | 
			
		||||
    snd_seq_port_info_alloca (&portInfo);
 | 
			
		||||
@ -406,7 +413,7 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput,
 | 
			
		||||
    AlsaClient::Port* port = nullptr;
 | 
			
		||||
    auto client = AlsaClient::getInstance();
 | 
			
		||||
 | 
			
		||||
    if (auto* seqHandle = client->get())
 | 
			
		||||
    if (auto seqHandle = client->get())
 | 
			
		||||
    {
 | 
			
		||||
        snd_seq_system_info_t* systemInfo = nullptr;
 | 
			
		||||
        snd_seq_client_info_t* clientInfo = nullptr;
 | 
			
		||||
@ -503,8 +510,7 @@ void MidiOutput::sendMessageNow (const MidiMessage& message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
MidiInput::MidiInput (const String& nm)
 | 
			
		||||
    : name (nm), internal (nullptr)
 | 
			
		||||
MidiInput::MidiInput (const String& nm)  : name (nm)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -538,8 +544,6 @@ StringArray MidiInput::getDevices()
 | 
			
		||||
 | 
			
		||||
MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
 | 
			
		||||
{
 | 
			
		||||
    MidiInput* newDevice = nullptr;
 | 
			
		||||
 | 
			
		||||
    StringArray devices;
 | 
			
		||||
    auto* port = iterateMidiDevices (true, devices, deviceIndex);
 | 
			
		||||
 | 
			
		||||
@ -548,26 +552,22 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
 | 
			
		||||
 | 
			
		||||
    jassert (port->isValid());
 | 
			
		||||
 | 
			
		||||
    newDevice = new MidiInput (devices [deviceIndex]);
 | 
			
		||||
    auto newDevice = new MidiInput (devices [deviceIndex]);
 | 
			
		||||
    port->setupInput (newDevice, callback);
 | 
			
		||||
    newDevice->internal = port;
 | 
			
		||||
 | 
			
		||||
    return newDevice;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
 | 
			
		||||
{
 | 
			
		||||
    MidiInput* newDevice = nullptr;
 | 
			
		||||
 | 
			
		||||
    auto client = AlsaClient::getInstance();
 | 
			
		||||
    auto* port = client->createPort (deviceName, true, true);
 | 
			
		||||
 | 
			
		||||
    jassert (port->isValid());
 | 
			
		||||
 | 
			
		||||
    newDevice = new MidiInput (deviceName);
 | 
			
		||||
    auto newDevice = new MidiInput (deviceName);
 | 
			
		||||
    port->setupInput (newDevice, callback);
 | 
			
		||||
    newDevice->internal = port;
 | 
			
		||||
 | 
			
		||||
    return newDevice;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -584,7 +584,7 @@ MidiOutput* MidiOutput::createNewDevice (const String&)             { return nul
 | 
			
		||||
MidiOutput::~MidiOutput()   {}
 | 
			
		||||
void MidiOutput::sendMessageNow (const MidiMessage&)    {}
 | 
			
		||||
 | 
			
		||||
MidiInput::MidiInput (const String& nm) : name (nm), internal (nullptr)  {}
 | 
			
		||||
MidiInput::MidiInput (const String& nm) : name (nm)  {}
 | 
			
		||||
MidiInput::~MidiInput() {}
 | 
			
		||||
void MidiInput::start() {}
 | 
			
		||||
void MidiInput::stop()  {}
 | 
			
		||||
 | 
			
		||||
@ -181,7 +181,7 @@ public:
 | 
			
		||||
        AudioObjectAddPropertyListener (deviceID, &pa, deviceListenerProc, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~CoreAudioInternal()
 | 
			
		||||
    ~CoreAudioInternal() override
 | 
			
		||||
    {
 | 
			
		||||
        AudioObjectPropertyAddress pa;
 | 
			
		||||
        pa.mSelector = kAudioObjectPropertySelectorWildcard;
 | 
			
		||||
@ -289,15 +289,17 @@ public:
 | 
			
		||||
 | 
			
		||||
            if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, ranges)))
 | 
			
		||||
            {
 | 
			
		||||
                static const double possibleRates[] = { 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0, 384000.0 };
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < numElementsInArray (possibleRates); ++i)
 | 
			
		||||
                for (auto r : { 8000, 11025, 16000, 22050, 32000,
 | 
			
		||||
                                44100, 48000, 88200, 96000, 176400,
 | 
			
		||||
                                192000, 352800, 384000, 705600, 768000 })
 | 
			
		||||
                {
 | 
			
		||||
                    auto rate = (double) r;
 | 
			
		||||
 | 
			
		||||
                    for (int j = size / (int) sizeof (AudioValueRange); --j >= 0;)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (possibleRates[i] >= ranges[j].mMinimum - 2 && possibleRates[i] <= ranges[j].mMaximum + 2)
 | 
			
		||||
                        if (rate >= ranges[j].mMinimum - 2 && rate <= ranges[j].mMaximum + 2)
 | 
			
		||||
                        {
 | 
			
		||||
                            newSampleRates.add (possibleRates[i]);
 | 
			
		||||
                            newSampleRates.add (rate);
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
@ -462,6 +464,9 @@ public:
 | 
			
		||||
            inputChannelInfo.swapWith (newInChans);
 | 
			
		||||
            outputChannelInfo.swapWith (newOutChans);
 | 
			
		||||
 | 
			
		||||
            numInputChans  = inputChannelInfo.size();
 | 
			
		||||
            numOutputChans = outputChannelInfo.size();
 | 
			
		||||
 | 
			
		||||
            allocateTempBuffers();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -574,7 +579,7 @@ public:
 | 
			
		||||
 | 
			
		||||
                OSType typeId = types[index];
 | 
			
		||||
 | 
			
		||||
                OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (typeId), &typeId));
 | 
			
		||||
                OK (AudioObjectSetPropertyData (deviceID, &pa, 0, nullptr, sizeof (typeId), &typeId));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -600,7 +605,7 @@ public:
 | 
			
		||||
        pa.mScope = kAudioObjectPropertyScopeGlobal;
 | 
			
		||||
        pa.mElement = kAudioObjectPropertyElementMaster;
 | 
			
		||||
        Float64 sr = newSampleRate;
 | 
			
		||||
        return OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (sr), &sr));
 | 
			
		||||
        return OK (AudioObjectSetPropertyData (deviceID, &pa, 0, nullptr, sizeof (sr), &sr));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
@ -643,7 +648,7 @@ public:
 | 
			
		||||
            pa.mElement = kAudioObjectPropertyElementMaster;
 | 
			
		||||
            UInt32 framesPerBuf = (UInt32) bufferSizeSamples;
 | 
			
		||||
 | 
			
		||||
            if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (framesPerBuf), &framesPerBuf)))
 | 
			
		||||
            if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, nullptr, sizeof (framesPerBuf), &framesPerBuf)))
 | 
			
		||||
            {
 | 
			
		||||
                updateDetailsFromDevice();
 | 
			
		||||
                error = "Couldn't change buffer size";
 | 
			
		||||
@ -686,7 +691,7 @@ public:
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID));
 | 
			
		||||
                        audioProcID = 0;
 | 
			
		||||
                        audioProcID = {};
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -712,7 +717,7 @@ public:
 | 
			
		||||
        {
 | 
			
		||||
            OK (AudioDeviceStop (deviceID, audioIOProc));
 | 
			
		||||
            OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID));
 | 
			
		||||
            audioProcID = 0;
 | 
			
		||||
            audioProcID = {};
 | 
			
		||||
 | 
			
		||||
            started = false;
 | 
			
		||||
 | 
			
		||||
@ -776,7 +781,7 @@ public:
 | 
			
		||||
 | 
			
		||||
            for (int i = numOutputChans; --i >= 0;)
 | 
			
		||||
            {
 | 
			
		||||
                auto& info = outputChannelInfo.getReference(i);
 | 
			
		||||
                auto& info = outputChannelInfo.getReference (i);
 | 
			
		||||
                auto src = tempOutputBuffers[i];
 | 
			
		||||
                auto dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples;
 | 
			
		||||
                auto stride = info.dataStrideSamples;
 | 
			
		||||
@ -815,8 +820,8 @@ public:
 | 
			
		||||
        const int oldBufferSize = bufferSize;
 | 
			
		||||
 | 
			
		||||
        if (! updateDetailsFromDevice())
 | 
			
		||||
            owner.stop();
 | 
			
		||||
        else if (oldBufferSize != bufferSize || oldSampleRate != sampleRate)
 | 
			
		||||
            owner.stopInternal();
 | 
			
		||||
        else if ((oldBufferSize != bufferSize || oldSampleRate != sampleRate) && owner.shouldRestartDevice())
 | 
			
		||||
            owner.restart();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -831,7 +836,7 @@ public:
 | 
			
		||||
    Array<double> sampleRates;
 | 
			
		||||
    Array<int> bufferSizes;
 | 
			
		||||
    AudioIODeviceCallback* callback = nullptr;
 | 
			
		||||
    AudioDeviceIOProcID audioProcID = 0;
 | 
			
		||||
    AudioDeviceIOProcID audioProcID = {};
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    CriticalSection callbackLock;
 | 
			
		||||
@ -882,7 +887,9 @@ private:
 | 
			
		||||
            case kAudioDevicePropertyDeviceHasChanged:
 | 
			
		||||
            case kAudioObjectPropertyOwnedObjects:
 | 
			
		||||
                intern->owner.restart();
 | 
			
		||||
                intern->owner.deviceType.triggerAsyncAudioDeviceListChange();
 | 
			
		||||
 | 
			
		||||
                if (intern->owner.deviceType != nullptr)
 | 
			
		||||
                    intern->owner.deviceType->triggerAsyncAudioDeviceListChange();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case kAudioDevicePropertyBufferSizeRange:
 | 
			
		||||
@ -941,16 +948,14 @@ class CoreAudioIODevice   : public AudioIODevice,
 | 
			
		||||
                            private Timer
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    CoreAudioIODevice (CoreAudioIODeviceType& dt,
 | 
			
		||||
    CoreAudioIODevice (CoreAudioIODeviceType* dt,
 | 
			
		||||
                       const String& deviceName,
 | 
			
		||||
                       AudioDeviceID inputDeviceId, const int inputIndex_,
 | 
			
		||||
                       AudioDeviceID outputDeviceId, const int outputIndex_)
 | 
			
		||||
        : AudioIODevice (deviceName, "CoreAudio"),
 | 
			
		||||
          deviceType (dt),
 | 
			
		||||
          inputIndex (inputIndex_),
 | 
			
		||||
          outputIndex (outputIndex_),
 | 
			
		||||
          isOpen_ (false),
 | 
			
		||||
          isStarted (false)
 | 
			
		||||
          outputIndex (outputIndex_)
 | 
			
		||||
    {
 | 
			
		||||
        CoreAudioInternal* device = nullptr;
 | 
			
		||||
 | 
			
		||||
@ -975,7 +980,7 @@ public:
 | 
			
		||||
        AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~CoreAudioIODevice()
 | 
			
		||||
    ~CoreAudioIODevice() override
 | 
			
		||||
    {
 | 
			
		||||
        close();
 | 
			
		||||
 | 
			
		||||
@ -1077,6 +1082,8 @@ public:
 | 
			
		||||
 | 
			
		||||
    void stop() override
 | 
			
		||||
    {
 | 
			
		||||
        restartDevice = false;
 | 
			
		||||
 | 
			
		||||
        if (isStarted)
 | 
			
		||||
        {
 | 
			
		||||
            AudioIODeviceCallback* const lastCallback = internal->callback;
 | 
			
		||||
@ -1089,6 +1096,12 @@ public:
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void stopInternal()
 | 
			
		||||
    {
 | 
			
		||||
        stop();
 | 
			
		||||
        restartDevice = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool isPlaying() override
 | 
			
		||||
    {
 | 
			
		||||
        if (internal->callback == nullptr)
 | 
			
		||||
@ -1104,7 +1117,8 @@ public:
 | 
			
		||||
 | 
			
		||||
    void audioDeviceListChanged()
 | 
			
		||||
    {
 | 
			
		||||
        deviceType.audioDeviceListChanged();
 | 
			
		||||
        if (deviceType != nullptr)
 | 
			
		||||
            deviceType->audioDeviceListChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void restart()
 | 
			
		||||
@ -1123,7 +1137,7 @@ public:
 | 
			
		||||
                    if (internal->callback != nullptr)
 | 
			
		||||
                        previousCallback = internal->callback;
 | 
			
		||||
 | 
			
		||||
                    stop();
 | 
			
		||||
                    stopInternal();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1141,12 +1155,14 @@ public:
 | 
			
		||||
        deviceWrapperRestartCallback = cb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CoreAudioIODeviceType& deviceType;
 | 
			
		||||
    bool shouldRestartDevice() const noexcept    { return restartDevice; }
 | 
			
		||||
 | 
			
		||||
    WeakReference<CoreAudioIODeviceType> deviceType;
 | 
			
		||||
    int inputIndex, outputIndex;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::unique_ptr<CoreAudioInternal> internal;
 | 
			
		||||
    bool isOpen_, isStarted;
 | 
			
		||||
    bool isOpen_ = false, isStarted = false, restartDevice = true;
 | 
			
		||||
    String lastError;
 | 
			
		||||
    AudioIODeviceCallback* previousCallback = nullptr;
 | 
			
		||||
    std::function<void()> deviceWrapperRestartCallback = nullptr;
 | 
			
		||||
@ -1159,7 +1175,7 @@ private:
 | 
			
		||||
    {
 | 
			
		||||
        stopTimer();
 | 
			
		||||
 | 
			
		||||
        stop();
 | 
			
		||||
        stopInternal();
 | 
			
		||||
 | 
			
		||||
        internal->updateDetailsFromDevice();
 | 
			
		||||
 | 
			
		||||
@ -1194,14 +1210,14 @@ class AudioIODeviceCombiner    : public AudioIODevice,
 | 
			
		||||
                                 private Timer
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AudioIODeviceCombiner (const String& deviceName, CoreAudioIODeviceType& deviceType)
 | 
			
		||||
    AudioIODeviceCombiner (const String& deviceName, CoreAudioIODeviceType* deviceType)
 | 
			
		||||
        : AudioIODevice (deviceName, "CoreAudio"),
 | 
			
		||||
          Thread (deviceName),
 | 
			
		||||
          owner (deviceType)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~AudioIODeviceCombiner()
 | 
			
		||||
    ~AudioIODeviceCombiner() override
 | 
			
		||||
    {
 | 
			
		||||
        close();
 | 
			
		||||
        devices.clear();
 | 
			
		||||
@ -1548,7 +1564,7 @@ public:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    CoreAudioIODeviceType& owner;
 | 
			
		||||
    WeakReference<CoreAudioIODeviceType> owner;
 | 
			
		||||
    CriticalSection callbackLock;
 | 
			
		||||
    AudioIODeviceCallback* callback = nullptr;
 | 
			
		||||
    AudioIODeviceCallback* previousCallback = nullptr;
 | 
			
		||||
@ -1641,7 +1657,7 @@ private:
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto* d : devices)
 | 
			
		||||
            d->device->stop();
 | 
			
		||||
            d->device->stopInternal();
 | 
			
		||||
 | 
			
		||||
        if (lastCallback != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
@ -1770,8 +1786,8 @@ private:
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (anySampleRateChanges)
 | 
			
		||||
            owner.audioDeviceListChanged();
 | 
			
		||||
        if (anySampleRateChanges && owner != nullptr)
 | 
			
		||||
            owner->audioDeviceListChanged();
 | 
			
		||||
 | 
			
		||||
        if (callback != nullptr)
 | 
			
		||||
            callback->audioDeviceAboutToStart (device);
 | 
			
		||||
@ -1790,7 +1806,7 @@ private:
 | 
			
		||||
            d->setDeviceWrapperRestartCallback ([this] { owner.restartAsync(); });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ~DeviceWrapper()
 | 
			
		||||
        ~DeviceWrapper() override
 | 
			
		||||
        {
 | 
			
		||||
            close();
 | 
			
		||||
        }
 | 
			
		||||
@ -2002,7 +2018,7 @@ public:
 | 
			
		||||
        AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~CoreAudioIODeviceType()
 | 
			
		||||
    ~CoreAudioIODeviceType() override
 | 
			
		||||
    {
 | 
			
		||||
        AudioObjectPropertyAddress pa;
 | 
			
		||||
        pa.mSelector = kAudioHardwarePropertyDevices;
 | 
			
		||||
@ -2155,20 +2171,20 @@ public:
 | 
			
		||||
                                                       : outputDeviceName;
 | 
			
		||||
 | 
			
		||||
        if (inputDeviceID == outputDeviceID)
 | 
			
		||||
            return new CoreAudioIODevice (*this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex);
 | 
			
		||||
            return new CoreAudioIODevice (this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex);
 | 
			
		||||
 | 
			
		||||
        std::unique_ptr<CoreAudioIODevice> in, out;
 | 
			
		||||
 | 
			
		||||
        if (inputDeviceID != 0)
 | 
			
		||||
            in.reset (new CoreAudioIODevice (*this, inputDeviceName, inputDeviceID, inputIndex, 0, -1));
 | 
			
		||||
            in.reset (new CoreAudioIODevice (this, inputDeviceName, inputDeviceID, inputIndex, 0, -1));
 | 
			
		||||
 | 
			
		||||
        if (outputDeviceID != 0)
 | 
			
		||||
            out.reset (new CoreAudioIODevice (*this, outputDeviceName, 0, -1, outputDeviceID, outputIndex));
 | 
			
		||||
            out.reset (new CoreAudioIODevice (this, outputDeviceName, 0, -1, outputDeviceID, outputIndex));
 | 
			
		||||
 | 
			
		||||
        if (in == nullptr)   return out.release();
 | 
			
		||||
        if (out == nullptr)  return in.release();
 | 
			
		||||
 | 
			
		||||
        std::unique_ptr<AudioIODeviceCombiner> combo (new AudioIODeviceCombiner (combinedName, *this));
 | 
			
		||||
        std::unique_ptr<AudioIODeviceCombiner> combo (new AudioIODeviceCombiner (combinedName, this));
 | 
			
		||||
        combo->addDevice (in.release(),  true, false);
 | 
			
		||||
        combo->addDevice (out.release(), false, true);
 | 
			
		||||
        return combo.release();
 | 
			
		||||
@ -2230,6 +2246,7 @@ private:
 | 
			
		||||
        audioDeviceListChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    JUCE_DECLARE_WEAK_REFERENCEABLE (CoreAudioIODeviceType)
 | 
			
		||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -218,35 +218,6 @@ namespace CoreMidiHelpers
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static StringArray findDevices (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());
 | 
			
		||||
 | 
			
		||||
        StringArray s;
 | 
			
		||||
        enableSimulatorMidiSession();
 | 
			
		||||
 | 
			
		||||
        auto num = forInput ? MIDIGetNumberOfSources()
 | 
			
		||||
                            : MIDIGetNumberOfDestinations();
 | 
			
		||||
 | 
			
		||||
        for (ItemCount i = 0; i < num; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            String name;
 | 
			
		||||
 | 
			
		||||
            if (auto dest = forInput ? MIDIGetSource (i)
 | 
			
		||||
                                     : MIDIGetDestination (i))
 | 
			
		||||
                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..
 | 
			
		||||
@ -268,7 +239,7 @@ namespace CoreMidiHelpers
 | 
			
		||||
        {
 | 
			
		||||
            // Since OSX 10.6, the MIDIClientCreate function will only work
 | 
			
		||||
            // correctly when called from the message thread!
 | 
			
		||||
            jassert (MessageManager::getInstance()->isThisTheMessageThread());
 | 
			
		||||
            JUCE_ASSERT_MESSAGE_THREAD
 | 
			
		||||
 | 
			
		||||
            enableSimulatorMidiSession();
 | 
			
		||||
 | 
			
		||||
@ -280,6 +251,40 @@ namespace CoreMidiHelpers
 | 
			
		||||
        return globalMidiClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static StringArray findDevices (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.
 | 
			
		||||
        JUCE_ASSERT_MESSAGE_THREAD
 | 
			
		||||
 | 
			
		||||
        if (getGlobalMidiClient() == 0)
 | 
			
		||||
        {
 | 
			
		||||
            jassertfalse;
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        StringArray s;
 | 
			
		||||
        enableSimulatorMidiSession();
 | 
			
		||||
 | 
			
		||||
        auto num = forInput ? MIDIGetNumberOfSources()
 | 
			
		||||
                            : MIDIGetNumberOfDestinations();
 | 
			
		||||
 | 
			
		||||
        for (ItemCount i = 0; i < num; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            String name;
 | 
			
		||||
 | 
			
		||||
            if (auto dest = forInput ? MIDIGetSource (i) : MIDIGetDestination (i))
 | 
			
		||||
                name = getConnectedEndpointName (dest);
 | 
			
		||||
 | 
			
		||||
            if (name.isEmpty())
 | 
			
		||||
                name = "<error>";
 | 
			
		||||
 | 
			
		||||
            s.add (name);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    class MidiPortAndEndpoint
 | 
			
		||||
    {
 | 
			
		||||
@ -344,8 +349,8 @@ namespace CoreMidiHelpers
 | 
			
		||||
 | 
			
		||||
                for (unsigned int i = 0; i < pktlist->numPackets; ++i)
 | 
			
		||||
                {
 | 
			
		||||
                    concatenator.pushMidiData (packet->data, (int) packet->length, time,
 | 
			
		||||
                                               input, callback);
 | 
			
		||||
                    auto len = readUnaligned<decltype (packet->length)> (&(packet->length));
 | 
			
		||||
                    concatenator.pushMidiData (packet->data, (int) len, time, input, callback);
 | 
			
		||||
 | 
			
		||||
                    packet = MIDIPacketNext (packet);
 | 
			
		||||
                }
 | 
			
		||||
@ -375,22 +380,24 @@ MidiOutput* MidiOutput::openDevice (int index)
 | 
			
		||||
{
 | 
			
		||||
    MidiOutput* mo = nullptr;
 | 
			
		||||
 | 
			
		||||
    if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations()))
 | 
			
		||||
    if (auto client = CoreMidiHelpers::getGlobalMidiClient())
 | 
			
		||||
    {
 | 
			
		||||
        auto endPoint = MIDIGetDestination ((ItemCount) index);
 | 
			
		||||
 | 
			
		||||
        CoreMidiHelpers::ScopedCFString pname;
 | 
			
		||||
 | 
			
		||||
        if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString)))
 | 
			
		||||
        if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations()))
 | 
			
		||||
        {
 | 
			
		||||
            auto client = CoreMidiHelpers::getGlobalMidiClient();
 | 
			
		||||
            MIDIPortRef port;
 | 
			
		||||
            auto deviceName = CoreMidiHelpers::getConnectedEndpointName (endPoint);
 | 
			
		||||
            auto endPoint = MIDIGetDestination ((ItemCount) index);
 | 
			
		||||
 | 
			
		||||
            if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port)))
 | 
			
		||||
            CoreMidiHelpers::ScopedCFString pname;
 | 
			
		||||
 | 
			
		||||
            if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString)))
 | 
			
		||||
            {
 | 
			
		||||
                mo = new MidiOutput (deviceName);
 | 
			
		||||
                mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint);
 | 
			
		||||
                MIDIPortRef port;
 | 
			
		||||
                auto deviceName = CoreMidiHelpers::getConnectedEndpointName (endPoint);
 | 
			
		||||
 | 
			
		||||
                if (CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port)))
 | 
			
		||||
                {
 | 
			
		||||
                    mo = new MidiOutput (deviceName);
 | 
			
		||||
                    mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -400,19 +407,21 @@ MidiOutput* MidiOutput::openDevice (int index)
 | 
			
		||||
 | 
			
		||||
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)))
 | 
			
		||||
    if (auto client = CoreMidiHelpers::getGlobalMidiClient())
 | 
			
		||||
    {
 | 
			
		||||
        CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false);
 | 
			
		||||
        MIDIEndpointRef endPoint;
 | 
			
		||||
 | 
			
		||||
        auto mo = new MidiOutput (deviceName);
 | 
			
		||||
        mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint);
 | 
			
		||||
        return mo;
 | 
			
		||||
        CoreMidiHelpers::ScopedCFString name;
 | 
			
		||||
        name.cfString = deviceName.toCFString();
 | 
			
		||||
 | 
			
		||||
        if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint)))
 | 
			
		||||
        {
 | 
			
		||||
            CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false);
 | 
			
		||||
 | 
			
		||||
            auto mo = new MidiOutput (deviceName);
 | 
			
		||||
            mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint);
 | 
			
		||||
            return mo;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
@ -495,15 +504,15 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
 | 
			
		||||
    using namespace CoreMidiHelpers;
 | 
			
		||||
    MidiInput* newInput = nullptr;
 | 
			
		||||
 | 
			
		||||
    if (isPositiveAndBelow (index, MIDIGetNumberOfSources()))
 | 
			
		||||
    if (auto client = getGlobalMidiClient())
 | 
			
		||||
    {
 | 
			
		||||
        if (auto endPoint = MIDIGetSource ((ItemCount) index))
 | 
			
		||||
        if (isPositiveAndBelow (index, MIDIGetNumberOfSources()))
 | 
			
		||||
        {
 | 
			
		||||
            ScopedCFString name;
 | 
			
		||||
 | 
			
		||||
            if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString)))
 | 
			
		||||
            if (auto endPoint = MIDIGetSource ((ItemCount) index))
 | 
			
		||||
            {
 | 
			
		||||
                if (auto client = getGlobalMidiClient())
 | 
			
		||||
                ScopedCFString name;
 | 
			
		||||
 | 
			
		||||
                if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString)))
 | 
			
		||||
                {
 | 
			
		||||
                    MIDIPortRef port;
 | 
			
		||||
                    std::unique_ptr<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback));
 | 
			
		||||
 | 
			
		||||
@ -346,11 +346,11 @@ public:
 | 
			
		||||
 | 
			
		||||
        if (asioObject != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            const int possibleSampleRates[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
 | 
			
		||||
 | 
			
		||||
            for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index)
 | 
			
		||||
                if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0)
 | 
			
		||||
                    newRates.add ((double) possibleSampleRates[index]);
 | 
			
		||||
            for (auto rate : { 8000, 11025, 16000, 22050, 32000,
 | 
			
		||||
                               44100, 48000, 88200, 96000, 176400,
 | 
			
		||||
                               192000, 352800, 384000, 705600, 768000 })
 | 
			
		||||
                if (asioObject->canSampleRate ((double) rate) == 0)
 | 
			
		||||
                    newRates.add ((double) rate);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newRates.isEmpty())
 | 
			
		||||
@ -770,7 +770,7 @@ private:
 | 
			
		||||
        if (CharPointer_UTF8::isValidString (text, length))
 | 
			
		||||
            return String::fromUTF8 (text, length);
 | 
			
		||||
 | 
			
		||||
        WCHAR wideVersion[64] = {};
 | 
			
		||||
        WCHAR wideVersion[512] = {};
 | 
			
		||||
        MultiByteToWideChar (CP_ACP, 0, text, length, wideVersion, numElementsInArray (wideVersion));
 | 
			
		||||
        return wideVersion;
 | 
			
		||||
    }
 | 
			
		||||
@ -787,9 +787,14 @@ private:
 | 
			
		||||
 | 
			
		||||
    void reloadChannelNames()
 | 
			
		||||
    {
 | 
			
		||||
        long totalInChannels = 0, totalOutChannels = 0;
 | 
			
		||||
 | 
			
		||||
        if (asioObject != nullptr
 | 
			
		||||
             && asioObject->getChannels (&totalNumInputChans, &totalNumOutputChans) == ASE_OK)
 | 
			
		||||
             && asioObject->getChannels (&totalInChannels, &totalOutChannels) == ASE_OK)
 | 
			
		||||
        {
 | 
			
		||||
            totalNumInputChans  = totalInChannels;
 | 
			
		||||
            totalNumOutputChans = totalOutChannels;
 | 
			
		||||
 | 
			
		||||
            inputChannelNames.clear();
 | 
			
		||||
            outputChannelNames.clear();
 | 
			
		||||
 | 
			
		||||
@ -1108,9 +1113,11 @@ private:
 | 
			
		||||
    String getLastDriverError() const
 | 
			
		||||
    {
 | 
			
		||||
        jassert (asioObject != nullptr);
 | 
			
		||||
 | 
			
		||||
        char buffer[512] = {};
 | 
			
		||||
        asioObject->getErrorMessage (buffer);
 | 
			
		||||
        return String (buffer, sizeof (buffer) - 1);
 | 
			
		||||
 | 
			
		||||
        return convertASIOString (buffer, sizeof (buffer));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String initDriver()
 | 
			
		||||
@ -1420,7 +1427,12 @@ public:
 | 
			
		||||
            TCHAR name[256] = {};
 | 
			
		||||
 | 
			
		||||
            while (RegEnumKey (hk, index++, name, numElementsInArray (name)) == ERROR_SUCCESS)
 | 
			
		||||
            {
 | 
			
		||||
                if (isBlacklistedDriver (name))
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                addDriverInfo (name, hk);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RegCloseKey (hk);
 | 
			
		||||
        }
 | 
			
		||||
@ -1552,6 +1564,11 @@ private:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static bool isBlacklistedDriver (const String& driverName)
 | 
			
		||||
    {
 | 
			
		||||
        return driverName == "ASIO DirectX Full Duplex Driver" || driverName == "ASIO Multimedia Driver";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void addDriverInfo (const String& keyName, HKEY hk)
 | 
			
		||||
    {
 | 
			
		||||
        HKEY subKey;
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -352,8 +352,8 @@ void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcep
 | 
			
		||||
class WASAPIDeviceBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode)
 | 
			
		||||
        : device (d), useExclusiveMode (exclusiveMode)
 | 
			
		||||
    WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode, std::function<void()>&& cb)
 | 
			
		||||
        : device (d), useExclusiveMode (exclusiveMode), reopenCallback (cb)
 | 
			
		||||
    {
 | 
			
		||||
        clientEvent = CreateEvent (nullptr, false, false, nullptr);
 | 
			
		||||
 | 
			
		||||
@ -387,21 +387,21 @@ public:
 | 
			
		||||
            // Got a format that is supported by the device so we can ask what sample rates are supported (in whatever format)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static const int ratesToTest[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < numElementsInArray (ratesToTest); ++i)
 | 
			
		||||
        {
 | 
			
		||||
            if (rates.contains (ratesToTest[i]))
 | 
			
		||||
        for (auto rate : { 8000, 11025, 16000, 22050, 32000,
 | 
			
		||||
                           44100, 48000, 88200, 96000, 176400,
 | 
			
		||||
                           192000, 352800, 384000, 705600, 768000 })
 | 
			
		||||
       {
 | 
			
		||||
            if (rates.contains (rate))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            format.Format.nSamplesPerSec  = (DWORD) ratesToTest[i];
 | 
			
		||||
            format.Format.nSamplesPerSec  = (DWORD) rate;
 | 
			
		||||
            format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8);
 | 
			
		||||
 | 
			
		||||
            if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE
 | 
			
		||||
                                                                           : AUDCLNT_SHAREMODE_SHARED,
 | 
			
		||||
                                                          (WAVEFORMATEX*) &format, 0)))
 | 
			
		||||
                if (! rates.contains (ratesToTest[i]))
 | 
			
		||||
                    rates.addUsingDefaultSort (ratesToTest[i]);
 | 
			
		||||
                if (! rates.contains (rate))
 | 
			
		||||
                    rates.addUsingDefaultSort (rate);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -484,6 +484,7 @@ public:
 | 
			
		||||
    UINT32 actualBufferSize = 0;
 | 
			
		||||
    int bytesPerSample = 0, bytesPerFrame = 0;
 | 
			
		||||
    bool sampleRateHasChanged = false, shouldClose = false;
 | 
			
		||||
    std::function<void()> reopenCallback;
 | 
			
		||||
 | 
			
		||||
    virtual void updateFormat (bool isFloat) = 0;
 | 
			
		||||
 | 
			
		||||
@ -501,6 +502,9 @@ private:
 | 
			
		||||
 | 
			
		||||
        JUCE_COMRESULT OnStateChanged(AudioSessionState state)
 | 
			
		||||
        {
 | 
			
		||||
            if (state == AudioSessionStateActive)
 | 
			
		||||
                owner.reopenCallback();
 | 
			
		||||
 | 
			
		||||
            if (state == AudioSessionStateInactive || state == AudioSessionStateExpired)
 | 
			
		||||
                owner.deviceBecameInactive();
 | 
			
		||||
 | 
			
		||||
@ -509,7 +513,6 @@ private:
 | 
			
		||||
 | 
			
		||||
        JUCE_COMRESULT OnSessionDisconnected (AudioSessionDisconnectReason reason)
 | 
			
		||||
        {
 | 
			
		||||
            Logger::writeToLog("OnSessionDisconnected");
 | 
			
		||||
            if (reason == DisconnectReasonFormatChanged)
 | 
			
		||||
                owner.deviceSampleRateChanged();
 | 
			
		||||
 | 
			
		||||
@ -685,8 +688,8 @@ private:
 | 
			
		||||
class WASAPIInputDevice  : public WASAPIDeviceBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode)
 | 
			
		||||
        : WASAPIDeviceBase (d, exclusiveMode)
 | 
			
		||||
    WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode, std::function<void()>&& reopenCallback)
 | 
			
		||||
        : WASAPIDeviceBase (d, exclusiveMode, std::move (reopenCallback))
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -846,8 +849,8 @@ private:
 | 
			
		||||
class WASAPIOutputDevice  : public WASAPIDeviceBase
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode)
 | 
			
		||||
        : WASAPIDeviceBase (d, exclusiveMode)
 | 
			
		||||
    WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode, std::function<void()>&& reopenCallback)
 | 
			
		||||
        : WASAPIDeviceBase (d, exclusiveMode, std::move (reopenCallback))
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -982,6 +985,7 @@ public:
 | 
			
		||||
 | 
			
		||||
    ~WASAPIAudioIODevice()
 | 
			
		||||
    {
 | 
			
		||||
        cancelPendingUpdate();
 | 
			
		||||
        close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1278,7 +1282,7 @@ public:
 | 
			
		||||
                    outs.clear();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (outputDevice != nullptr && !deviceBecameInactive)
 | 
			
		||||
            if (outputDevice != nullptr && ! deviceBecameInactive)
 | 
			
		||||
            {
 | 
			
		||||
                // Note that this function is handed the input device so it can check for the event and make sure
 | 
			
		||||
                // the input reservoir is filled up correctly even when bufferSize > device actualBufferSize
 | 
			
		||||
@ -1358,34 +1362,46 @@ private:
 | 
			
		||||
 | 
			
		||||
            auto flow = getDataFlow (device);
 | 
			
		||||
 | 
			
		||||
            auto deviceReopenCallback = [this]
 | 
			
		||||
            {
 | 
			
		||||
                if (deviceBecameInactive)
 | 
			
		||||
                {
 | 
			
		||||
                    deviceBecameInactive = false;
 | 
			
		||||
                    MessageManager::callAsync ([this] { reopenDevices(); });
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if (deviceId == inputDeviceId && flow == eCapture)
 | 
			
		||||
                inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode));
 | 
			
		||||
                inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode, deviceReopenCallback));
 | 
			
		||||
            else if (deviceId == outputDeviceId && flow == eRender)
 | 
			
		||||
                outputDevice.reset (new WASAPIOutputDevice (device, useExclusiveMode));
 | 
			
		||||
                outputDevice.reset (new WASAPIOutputDevice (device, useExclusiveMode, deviceReopenCallback));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk()))
 | 
			
		||||
             && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void reopenDevices()
 | 
			
		||||
    {
 | 
			
		||||
        outputDevice = nullptr;
 | 
			
		||||
        inputDevice = nullptr;
 | 
			
		||||
 | 
			
		||||
        initialise();
 | 
			
		||||
 | 
			
		||||
        open (lastKnownInputChannels, lastKnownOutputChannels,
 | 
			
		||||
              getChangedSampleRate(), currentBufferSizeSamples);
 | 
			
		||||
 | 
			
		||||
        start (callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    void handleAsyncUpdate() override
 | 
			
		||||
    {
 | 
			
		||||
        stop();
 | 
			
		||||
 | 
			
		||||
        outputDevice = nullptr;
 | 
			
		||||
        inputDevice = nullptr;
 | 
			
		||||
 | 
			
		||||
        // sample rate change
 | 
			
		||||
        if (! deviceBecameInactive)
 | 
			
		||||
        {
 | 
			
		||||
            initialise();
 | 
			
		||||
 | 
			
		||||
            open (lastKnownInputChannels, lastKnownOutputChannels,
 | 
			
		||||
                  getChangedSampleRate(), currentBufferSizeSamples);
 | 
			
		||||
 | 
			
		||||
            start (callback);
 | 
			
		||||
        }
 | 
			
		||||
            reopenDevices();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    double getChangedSampleRate() const
 | 
			
		||||
@ -1502,7 +1518,7 @@ private:
 | 
			
		||||
    class ChangeNotificationClient : public ComBaseClassHelper<IMMNotificationClient>
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        ChangeNotificationClient (WASAPIAudioIODeviceType& d)
 | 
			
		||||
        ChangeNotificationClient (WASAPIAudioIODeviceType* d)
 | 
			
		||||
            : ComBaseClassHelper<IMMNotificationClient> (0), device (d) {}
 | 
			
		||||
 | 
			
		||||
        HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR)                             { return notify(); }
 | 
			
		||||
@ -1512,9 +1528,15 @@ private:
 | 
			
		||||
        HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        WASAPIAudioIODeviceType& device;
 | 
			
		||||
        WeakReference<WASAPIAudioIODeviceType> device;
 | 
			
		||||
 | 
			
		||||
        HRESULT notify()   { device.triggerAsyncDeviceChangeCallback(); return S_OK; }
 | 
			
		||||
        HRESULT notify()
 | 
			
		||||
        {
 | 
			
		||||
            if (device != nullptr)
 | 
			
		||||
            device->triggerAsyncDeviceChangeCallback();
 | 
			
		||||
 | 
			
		||||
            return S_OK;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeNotificationClient)
 | 
			
		||||
    };
 | 
			
		||||
@ -1555,7 +1577,7 @@ private:
 | 
			
		||||
            if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            notifyClient = new ChangeNotificationClient (*this);
 | 
			
		||||
            notifyClient = new ChangeNotificationClient (this);
 | 
			
		||||
            enumerator->RegisterEndpointNotificationCallback (notifyClient);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1644,6 +1666,7 @@ private:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    JUCE_DECLARE_WEAK_REFERENCEABLE (WASAPIAudioIODeviceType)
 | 
			
		||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user