349 lines
11 KiB
C++
349 lines
11 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
The code included in this file is provided under the terms of the ISC license
|
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
|
without fee is hereby granted provided that the above copyright notice and
|
|
this permission notice appear in all copies.
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
|
METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \
|
|
METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \
|
|
METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
|
|
METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
|
|
METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \
|
|
METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;")
|
|
DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager")
|
|
#undef JNI_CLASS_MEMBERS
|
|
|
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
|
METHOD (start, "start", "()V" )\
|
|
METHOD (stop, "stop", "()V") \
|
|
METHOD (close, "close", "()V") \
|
|
METHOD (sendMidi, "sendMidi", "([BII)V")
|
|
DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort")
|
|
#undef JNI_CLASS_MEMBERS
|
|
|
|
|
|
//==============================================================================
|
|
class AndroidMidiInput
|
|
{
|
|
public:
|
|
AndroidMidiInput (MidiInput* midiInput, int portIdx,
|
|
juce::MidiInputCallback* midiInputCallback, jobject deviceManager)
|
|
: juceMidiInput (midiInput),
|
|
callback (midiInputCallback),
|
|
midiConcatenator (2048),
|
|
javaMidiDevice (getEnv()->CallObjectMethod (deviceManager,
|
|
MidiDeviceManager.openMidiInputPortWithJuceIndex,
|
|
(jint) portIdx,
|
|
(jlong) this))
|
|
{
|
|
}
|
|
|
|
~AndroidMidiInput()
|
|
{
|
|
if (jobject d = javaMidiDevice.get())
|
|
{
|
|
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
|
|
javaMidiDevice.clear();
|
|
}
|
|
}
|
|
|
|
bool isOpen() const noexcept
|
|
{
|
|
return javaMidiDevice != nullptr;
|
|
}
|
|
|
|
void start()
|
|
{
|
|
if (jobject d = javaMidiDevice.get())
|
|
getEnv()->CallVoidMethod (d, JuceMidiPort.start);
|
|
}
|
|
|
|
void stop()
|
|
{
|
|
if (jobject d = javaMidiDevice.get())
|
|
getEnv()->CallVoidMethod (d, JuceMidiPort.stop);
|
|
|
|
callback = nullptr;
|
|
}
|
|
|
|
void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
|
|
{
|
|
jassert (byteArray != nullptr);
|
|
jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr);
|
|
|
|
HeapBlock<uint8> buffer (static_cast<size_t> (len));
|
|
std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len));
|
|
|
|
midiConcatenator.pushMidiData (buffer.get(),
|
|
len, static_cast<double> (timestamp) * 1.0e-9,
|
|
juceMidiInput, *callback);
|
|
|
|
getEnv()->ReleaseByteArrayElements (byteArray, data, 0);
|
|
}
|
|
|
|
private:
|
|
MidiInput* juceMidiInput;
|
|
MidiInputCallback* callback;
|
|
MidiDataConcatenator midiConcatenator;
|
|
GlobalRef javaMidiDevice;
|
|
};
|
|
|
|
//==============================================================================
|
|
class AndroidMidiOutput
|
|
{
|
|
public:
|
|
AndroidMidiOutput (jobject midiDevice)
|
|
: javaMidiDevice (midiDevice)
|
|
{
|
|
}
|
|
|
|
~AndroidMidiOutput()
|
|
{
|
|
if (jobject d = javaMidiDevice.get())
|
|
{
|
|
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
|
|
javaMidiDevice.clear();
|
|
}
|
|
}
|
|
|
|
void send (jbyteArray byteArray, jint offset, jint len)
|
|
{
|
|
if (jobject d = javaMidiDevice.get())
|
|
getEnv()->CallVoidMethod (d,
|
|
JuceMidiPort.sendMidi,
|
|
byteArray, offset, len);
|
|
}
|
|
|
|
private:
|
|
GlobalRef javaMidiDevice;
|
|
};
|
|
|
|
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive,
|
|
void, (JNIEnv* env, jobject, jlong host, jbyteArray byteArray,
|
|
jint offset, jint count, jlong timestamp))
|
|
{
|
|
// Java may create a Midi thread which JUCE doesn't know about and this callback may be
|
|
// received on this thread. Java will have already created a JNI Env for this new thread,
|
|
// which we need to tell JUCE about
|
|
setEnv (env);
|
|
|
|
reinterpret_cast<AndroidMidiInput*> (host)->receive (byteArray, offset, count, timestamp);
|
|
}
|
|
|
|
//==============================================================================
|
|
class AndroidMidiDeviceManager
|
|
{
|
|
public:
|
|
AndroidMidiDeviceManager()
|
|
: deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager))
|
|
{
|
|
}
|
|
|
|
String getInputPortNameForJuceIndex (int idx)
|
|
{
|
|
if (jobject dm = deviceManager.get())
|
|
{
|
|
LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx));
|
|
return juceString (string);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
String getOutputPortNameForJuceIndex (int idx)
|
|
{
|
|
if (jobject dm = deviceManager.get())
|
|
{
|
|
LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx));
|
|
return juceString (string);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
StringArray getDevices (bool input)
|
|
{
|
|
if (jobject dm = deviceManager.get())
|
|
{
|
|
jobjectArray jDevices
|
|
= (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices
|
|
: MidiDeviceManager.getJuceAndroidMidiOutputDevices);
|
|
|
|
// Create a local reference as converting this
|
|
// to a JUCE string will call into JNI
|
|
LocalRef<jobjectArray> devices (jDevices);
|
|
return javaStringArrayToJuce (devices);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback)
|
|
{
|
|
if (jobject dm = deviceManager.get())
|
|
{
|
|
std::unique_ptr<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:
|
|
GlobalRef deviceManager;
|
|
};
|
|
|
|
//==============================================================================
|
|
StringArray MidiOutput::getDevices()
|
|
{
|
|
AndroidMidiDeviceManager manager;
|
|
return manager.getDevices (false);
|
|
}
|
|
|
|
int MidiOutput::getDefaultDeviceIndex()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
MidiOutput* MidiOutput::openDevice (int index)
|
|
{
|
|
if (index < 0)
|
|
return nullptr;
|
|
|
|
AndroidMidiDeviceManager manager;
|
|
|
|
String midiOutputName = manager.getOutputPortNameForJuceIndex (index);
|
|
|
|
if (midiOutputName.isEmpty())
|
|
{
|
|
// you supplied an invalid device index!
|
|
jassertfalse;
|
|
return nullptr;
|
|
}
|
|
|
|
if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index))
|
|
{
|
|
MidiOutput* retval = new MidiOutput (midiOutputName);
|
|
retval->internal = midiOutput;
|
|
|
|
return retval;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
MidiOutput::~MidiOutput()
|
|
{
|
|
stopBackgroundThread();
|
|
|
|
delete reinterpret_cast<AndroidMidiOutput*> (internal);
|
|
}
|
|
|
|
void MidiOutput::sendMessageNow (const MidiMessage& message)
|
|
{
|
|
if (AndroidMidiOutput* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal))
|
|
{
|
|
JNIEnv* env = getEnv();
|
|
const int messageSize = message.getRawDataSize();
|
|
|
|
LocalRef<jbyteArray> messageContent = LocalRef<jbyteArray> (env->NewByteArray (messageSize));
|
|
jbyteArray content = messageContent.get();
|
|
|
|
jbyte* rawBytes = env->GetByteArrayElements (content, nullptr);
|
|
std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (messageSize));
|
|
env->ReleaseByteArrayElements (content, rawBytes, 0);
|
|
|
|
androidMidi->send (content, (jint) 0, (jint) messageSize);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
MidiInput::MidiInput (const String& nm) : name (nm)
|
|
{
|
|
}
|
|
|
|
StringArray MidiInput::getDevices()
|
|
{
|
|
AndroidMidiDeviceManager manager;
|
|
return manager.getDevices (true);
|
|
}
|
|
|
|
int MidiInput::getDefaultDeviceIndex()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback)
|
|
{
|
|
if (index < 0)
|
|
return nullptr;
|
|
|
|
AndroidMidiDeviceManager manager;
|
|
|
|
String midiInputName (manager.getInputPortNameForJuceIndex (index));
|
|
|
|
if (midiInputName.isEmpty())
|
|
{
|
|
// you supplied an invalid device index!
|
|
jassertfalse;
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<MidiInput> midiInput (new MidiInput (midiInputName));
|
|
|
|
midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput.get(), callback);
|
|
|
|
return midiInput->internal != nullptr ? midiInput.release()
|
|
: nullptr;
|
|
}
|
|
|
|
void MidiInput::start()
|
|
{
|
|
if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
|
|
mi->start();
|
|
}
|
|
|
|
void MidiInput::stop()
|
|
{
|
|
if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
|
|
mi->stop();
|
|
}
|
|
|
|
MidiInput::~MidiInput()
|
|
{
|
|
delete reinterpret_cast<AndroidMidiInput*> (internal);
|
|
}
|
|
|
|
} // namespace juce
|