|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2015 - ROLI Ltd.
   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3
   Details of these licenses can be found at: www.gnu.org/licenses
   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
   ------------------------------------------------------------------------------
   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.
  ==============================================================================
*/
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \
 METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \
 METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
 METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
 METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \
 METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;")
 DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (start, "start", "()V" )\
 METHOD (stop, "stop", "()V") \
 METHOD (close, "close", "()V") \
 METHOD (sendMidi, "sendMidi", "([BII)V")
 DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort")
#undef JNI_CLASS_MEMBERS
//==============================================================================
class AndroidMidiInput
{
public:
    AndroidMidiInput (MidiInput* midiInput, int portIdx,
                      juce::MidiInputCallback* midiInputCallback, jobject deviceManager)
        : juceMidiInput (midiInput),
          callback (midiInputCallback),
          midiConcatenator (2048),
          javaMidiDevice (getEnv()->CallObjectMethod (deviceManager,
                                                      MidiDeviceManager.openMidiInputPortWithJuceIndex,
                                                      (jint) portIdx,
                                                      (jlong) this))
    {
    }
    ~AndroidMidiInput()
    {
        if (jobject d = javaMidiDevice.get())
        {
            getEnv()->CallVoidMethod (d, JuceMidiPort.close);
            javaMidiDevice.clear();
        }
    }
    bool isOpen() const noexcept
    {
        return javaMidiDevice != nullptr;
    }
    void start()
    {
        if (jobject d = javaMidiDevice.get())
            getEnv()->CallVoidMethod (d, JuceMidiPort.start);
    }
    void stop()
    {
        if (jobject d = javaMidiDevice.get())
            getEnv()->CallVoidMethod (d, JuceMidiPort.stop);
        callback = nullptr;
    }
    void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
    {
        jassert (byteArray != nullptr);
        jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr);
        HeapBlock<uint8> buffer (len);
        std::memcpy (buffer.getData(), data + offset, len);
        midiConcatenator.pushMidiData (buffer.getData(),
                                       len, static_cast<double> (timestamp) * 1.0e-9,
                                       juceMidiInput, *callback);
        getEnv()->ReleaseByteArrayElements (byteArray, data, 0);
    }
private:
    MidiInput* juceMidiInput;
    MidiInputCallback* callback;
    GlobalRef javaMidiDevice;
    MidiDataConcatenator midiConcatenator;
};
//==============================================================================
class AndroidMidiOutput
{
public:
    AndroidMidiOutput (jobject midiDevice)
        : javaMidiDevice (midiDevice)
    {
    }
    ~AndroidMidiOutput()
    {
        if (jobject d = javaMidiDevice.get())
        {
            getEnv()->CallVoidMethod (d, JuceMidiPort.close);
            javaMidiDevice.clear();
        }
    }
    void send (jbyteArray byteArray, jint offset, jint len)
    {
        if (jobject d = javaMidiDevice.get())
            getEnv()->CallVoidMethod (d,
                                      JuceMidiPort.sendMidi,
                                      byteArray, offset, len);
    }
private:
    GlobalRef javaMidiDevice;
};
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive,
                   void, (JNIEnv* env, jobject device, jlong host, jbyteArray byteArray,
                          jint offset, jint count, jlong timestamp))
{
    // Java may create a Midi thread which JUCE doesn't know about and this callback may be
    // received on this thread. Java will have already created a JNI Env for this new thread,
    // which we need to tell Juce about
    setEnv (env);
    reinterpret_cast<AndroidMidiInput*> (host)->receive (byteArray, offset, count, timestamp);
}
//==============================================================================
class AndroidMidiDeviceManager
{
public:
    AndroidMidiDeviceManager ()
        : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager))
    {
    }
    String getInputPortNameForJuceIndex (int idx)
    {
        if (jobject dm = deviceManager.get())
        {
            LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx));
            return juceString (string);
        }
        return String();
    }
    String getOutputPortNameForJuceIndex (int idx)
    {
        if (jobject dm = deviceManager.get())
        {
            LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx));
            return juceString (string);
        }
        return String();
    }
    StringArray getDevices (bool input)
    {
        if (jobject dm = deviceManager.get())
        {
            jobjectArray jDevices
                = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices
                                                                  : MidiDeviceManager.getJuceAndroidMidiOutputDevices);
            // Create a local reference as converting this
            // to a JUCE string will call into JNI
            LocalRef<jobjectArray> devices (jDevices);
            return javaStringArrayToJuce (devices);
        }
        return StringArray();
    }
    AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback)
    {
        if (jobject dm = deviceManager.get())
        {
            ScopedPointer<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm));
            if (androidMidiInput->isOpen())
                return androidMidiInput.release();
        }
        return nullptr;
    }
    AndroidMidiOutput* openMidiOutputPortWithIndex (int idx)
    {
        if (jobject dm = deviceManager.get())
            if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx))
                return new AndroidMidiOutput (javaMidiPort);
        return nullptr;
    }
private:
    static StringArray javaStringArrayToJuce (jobjectArray jStrings)
    {
        StringArray retval;
        JNIEnv* env = getEnv();
        const int count = env->GetArrayLength (jStrings);
        for (int i = 0; i < count; ++i)
        {
            LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (jStrings, i));
            retval.add (juceString (string));
        }
        return retval;
    }
    GlobalRef deviceManager;
};
//==============================================================================
StringArray MidiOutput::getDevices()
{
    AndroidMidiDeviceManager manager;
    return manager.getDevices (false);
}
int MidiOutput::getDefaultDeviceIndex()
{
    return 0;
}
MidiOutput* MidiOutput::openDevice (int index)
{
    if (index < 0)
        return nullptr;
    AndroidMidiDeviceManager manager;
    String midiOutputName = manager.getOutputPortNameForJuceIndex (index);
    if (midiOutputName.isEmpty())
    {
        // you supplied an invalid device index!
        jassertfalse;
        return nullptr;
    }
    if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index))
    {
        MidiOutput* retval = new MidiOutput (midiOutputName);
        retval->internal = midiOutput;
        return retval;
    }
    return nullptr;
}
MidiOutput::~MidiOutput()
{
    stopBackgroundThread();
    delete reinterpret_cast<AndroidMidiOutput*> (internal);
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
    if (AndroidMidiOutput* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal))
    {
        JNIEnv* env = getEnv();
        const int messageSize = message.getRawDataSize();
        LocalRef<jbyteArray> messageContent = LocalRef<jbyteArray> (env->NewByteArray (messageSize));
        jbyteArray content = messageContent.get();
        jbyte* rawBytes = env->GetByteArrayElements (content, nullptr);
        std::memcpy (rawBytes, message.getRawData(), messageSize);
        env->ReleaseByteArrayElements (content, rawBytes, 0);
        androidMidi->send (content, (jint) 0, (jint) messageSize);
    }
}
//==============================================================================
MidiInput::MidiInput (const String& nm)  : name (nm)
{
}
StringArray MidiInput::getDevices()
{
    AndroidMidiDeviceManager manager;
    return manager.getDevices (true);
}
int MidiInput::getDefaultDeviceIndex()
{
    return 0;
}
MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback)
{
    if (index < 0)
        return nullptr;
    AndroidMidiDeviceManager manager;
    String midiInputName = manager.getInputPortNameForJuceIndex (index);
    if (midiInputName.isEmpty())
    {
        // you supplied an invalid device index!
        jassertfalse;
        return nullptr;
    }
    ScopedPointer<MidiInput> midiInput (new MidiInput (midiInputName));
    midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput, callback);
    return midiInput->internal != nullptr ? midiInput.release()
                                          : nullptr;
}
void MidiInput::start()
{
    if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
        mi->start();
}
void MidiInput::stop()
{
    if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
        mi->stop();
}
MidiInput::~MidiInput()
{
    delete reinterpret_cast<AndroidMidiInput*> (internal);
}
 |