Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

362 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  18. METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \
  19. METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \
  20. METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
  21. METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
  22. METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \
  23. METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;")
  24. DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager")
  25. #undef JNI_CLASS_MEMBERS
  26. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  27. METHOD (start, "start", "()V" )\
  28. METHOD (stop, "stop", "()V") \
  29. METHOD (close, "close", "()V") \
  30. METHOD (sendMidi, "sendMidi", "([BII)V")
  31. DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort")
  32. #undef JNI_CLASS_MEMBERS
  33. //==============================================================================
  34. class AndroidMidiInput
  35. {
  36. public:
  37. AndroidMidiInput (MidiInput* midiInput, int portIdx,
  38. juce::MidiInputCallback* midiInputCallback, jobject deviceManager)
  39. : juceMidiInput (midiInput),
  40. callback (midiInputCallback),
  41. midiConcatenator (2048),
  42. javaMidiDevice (getEnv()->CallObjectMethod (deviceManager,
  43. MidiDeviceManager.openMidiInputPortWithJuceIndex,
  44. (jint) portIdx,
  45. (jlong) this))
  46. {
  47. }
  48. ~AndroidMidiInput()
  49. {
  50. if (jobject d = javaMidiDevice.get())
  51. {
  52. getEnv()->CallVoidMethod (d, JuceMidiPort.close);
  53. javaMidiDevice.clear();
  54. }
  55. }
  56. bool isOpen() const noexcept
  57. {
  58. return javaMidiDevice != nullptr;
  59. }
  60. void start()
  61. {
  62. if (jobject d = javaMidiDevice.get())
  63. getEnv()->CallVoidMethod (d, JuceMidiPort.start);
  64. }
  65. void stop()
  66. {
  67. if (jobject d = javaMidiDevice.get())
  68. getEnv()->CallVoidMethod (d, JuceMidiPort.stop);
  69. callback = nullptr;
  70. }
  71. void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
  72. {
  73. jassert (byteArray != nullptr);
  74. jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr);
  75. HeapBlock<uint8> buffer (len);
  76. std::memcpy (buffer.getData(), data + offset, len);
  77. midiConcatenator.pushMidiData (buffer.getData(),
  78. len, static_cast<double> (timestamp) * 1.0e-9,
  79. juceMidiInput, *callback);
  80. getEnv()->ReleaseByteArrayElements (byteArray, data, 0);
  81. }
  82. private:
  83. MidiInput* juceMidiInput;
  84. MidiInputCallback* callback;
  85. GlobalRef javaMidiDevice;
  86. MidiDataConcatenator midiConcatenator;
  87. };
  88. //==============================================================================
  89. class AndroidMidiOutput
  90. {
  91. public:
  92. AndroidMidiOutput (jobject midiDevice)
  93. : javaMidiDevice (midiDevice)
  94. {
  95. }
  96. ~AndroidMidiOutput()
  97. {
  98. if (jobject d = javaMidiDevice.get())
  99. {
  100. getEnv()->CallVoidMethod (d, JuceMidiPort.close);
  101. javaMidiDevice.clear();
  102. }
  103. }
  104. void send (jbyteArray byteArray, jint offset, jint len)
  105. {
  106. if (jobject d = javaMidiDevice.get())
  107. getEnv()->CallVoidMethod (d,
  108. JuceMidiPort.sendMidi,
  109. byteArray, offset, len);
  110. }
  111. private:
  112. GlobalRef javaMidiDevice;
  113. };
  114. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive,
  115. void, (JNIEnv* env, jobject device, jlong host, jbyteArray byteArray,
  116. jint offset, jint count, jlong timestamp))
  117. {
  118. // Java may create a Midi thread which JUCE doesn't know about and this callback may be
  119. // received on this thread. Java will have already created a JNI Env for this new thread,
  120. // which we need to tell Juce about
  121. setEnv (env);
  122. reinterpret_cast<AndroidMidiInput*> (host)->receive (byteArray, offset, count, timestamp);
  123. }
  124. //==============================================================================
  125. class AndroidMidiDeviceManager
  126. {
  127. public:
  128. AndroidMidiDeviceManager()
  129. : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager))
  130. {
  131. }
  132. String getInputPortNameForJuceIndex (int idx)
  133. {
  134. if (jobject dm = deviceManager.get())
  135. {
  136. LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx));
  137. return juceString (string);
  138. }
  139. return String();
  140. }
  141. String getOutputPortNameForJuceIndex (int idx)
  142. {
  143. if (jobject dm = deviceManager.get())
  144. {
  145. LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx));
  146. return juceString (string);
  147. }
  148. return String();
  149. }
  150. StringArray getDevices (bool input)
  151. {
  152. if (jobject dm = deviceManager.get())
  153. {
  154. jobjectArray jDevices
  155. = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices
  156. : MidiDeviceManager.getJuceAndroidMidiOutputDevices);
  157. // Create a local reference as converting this
  158. // to a JUCE string will call into JNI
  159. LocalRef<jobjectArray> devices (jDevices);
  160. return javaStringArrayToJuce (devices);
  161. }
  162. return StringArray();
  163. }
  164. AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback)
  165. {
  166. if (jobject dm = deviceManager.get())
  167. {
  168. ScopedPointer<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm));
  169. if (androidMidiInput->isOpen())
  170. return androidMidiInput.release();
  171. }
  172. return nullptr;
  173. }
  174. AndroidMidiOutput* openMidiOutputPortWithIndex (int idx)
  175. {
  176. if (jobject dm = deviceManager.get())
  177. if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx))
  178. return new AndroidMidiOutput (javaMidiPort);
  179. return nullptr;
  180. }
  181. private:
  182. static StringArray javaStringArrayToJuce (jobjectArray jStrings)
  183. {
  184. StringArray retval;
  185. JNIEnv* env = getEnv();
  186. const int count = env->GetArrayLength (jStrings);
  187. for (int i = 0; i < count; ++i)
  188. {
  189. LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (jStrings, i));
  190. retval.add (juceString (string));
  191. }
  192. return retval;
  193. }
  194. GlobalRef deviceManager;
  195. };
  196. //==============================================================================
  197. StringArray MidiOutput::getDevices()
  198. {
  199. AndroidMidiDeviceManager manager;
  200. return manager.getDevices (false);
  201. }
  202. int MidiOutput::getDefaultDeviceIndex()
  203. {
  204. return 0;
  205. }
  206. MidiOutput* MidiOutput::openDevice (int index)
  207. {
  208. if (index < 0)
  209. return nullptr;
  210. AndroidMidiDeviceManager manager;
  211. String midiOutputName = manager.getOutputPortNameForJuceIndex (index);
  212. if (midiOutputName.isEmpty())
  213. {
  214. // you supplied an invalid device index!
  215. jassertfalse;
  216. return nullptr;
  217. }
  218. if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index))
  219. {
  220. MidiOutput* retval = new MidiOutput (midiOutputName);
  221. retval->internal = midiOutput;
  222. return retval;
  223. }
  224. return nullptr;
  225. }
  226. MidiOutput::~MidiOutput()
  227. {
  228. stopBackgroundThread();
  229. delete reinterpret_cast<AndroidMidiOutput*> (internal);
  230. }
  231. void MidiOutput::sendMessageNow (const MidiMessage& message)
  232. {
  233. if (AndroidMidiOutput* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal))
  234. {
  235. JNIEnv* env = getEnv();
  236. const int messageSize = message.getRawDataSize();
  237. LocalRef<jbyteArray> messageContent = LocalRef<jbyteArray> (env->NewByteArray (messageSize));
  238. jbyteArray content = messageContent.get();
  239. jbyte* rawBytes = env->GetByteArrayElements (content, nullptr);
  240. std::memcpy (rawBytes, message.getRawData(), messageSize);
  241. env->ReleaseByteArrayElements (content, rawBytes, 0);
  242. androidMidi->send (content, (jint) 0, (jint) messageSize);
  243. }
  244. }
  245. //==============================================================================
  246. MidiInput::MidiInput (const String& nm) : name (nm)
  247. {
  248. }
  249. StringArray MidiInput::getDevices()
  250. {
  251. AndroidMidiDeviceManager manager;
  252. return manager.getDevices (true);
  253. }
  254. int MidiInput::getDefaultDeviceIndex()
  255. {
  256. return 0;
  257. }
  258. MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback)
  259. {
  260. if (index < 0)
  261. return nullptr;
  262. AndroidMidiDeviceManager manager;
  263. String midiInputName = manager.getInputPortNameForJuceIndex (index);
  264. if (midiInputName.isEmpty())
  265. {
  266. // you supplied an invalid device index!
  267. jassertfalse;
  268. return nullptr;
  269. }
  270. ScopedPointer<MidiInput> midiInput (new MidiInput (midiInputName));
  271. midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput, callback);
  272. return midiInput->internal != nullptr ? midiInput.release()
  273. : nullptr;
  274. }
  275. void MidiInput::start()
  276. {
  277. if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
  278. mi->start();
  279. }
  280. void MidiInput::stop()
  281. {
  282. if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
  283. mi->stop();
  284. }
  285. MidiInput::~MidiInput()
  286. {
  287. delete reinterpret_cast<AndroidMidiInput*> (internal);
  288. }