Midi Device I/O via USB

Android doesn’t have a Midi API, but it does have a low level USB hardware api which can support USB Midi devices (USB Controllers, USB to DIN Cables) on Android 3.1+ devices that have a USB host port.
Examples of such devices include: Asus TF101, Acer A500, Galaxy Nexus

An Apache licensed library was recently released that lets you do this.

Hopefully this functionality will land in Juce eventually.

Thanks for the notification…

For what it’s worth, I did some generic USB support as a juce module, but only Mac, Linux, and Windows. When I looked at Android I found that my tablet was non-conforming for the USB toolkit and tabled it. But I was going to put together some HID stuff, so I’ll try to take another look at Android support sometime soon.

Just tested the USB-MIDI-Driver, hacking the juce_android_Midi.cpp very crudely so that it receives the midi events, and it works fine (the midi events are received). The code is a bit overengineered for juce as the ‘MIDIDriver’ parses the midi packets and provides callbacks for each midi message (onNoteOn, onNoteOff etc) instead of just passing raw bytes. I have the feeling that it could easily be rewritten into something shorter that would just fit into JUCE. Unfortunately my java knowledge is NULL.

Unfortunately, the audio out latency of my Nexus 7 is abysmally high, I would say between a quarter and half a second, even when I select the ‘256 samples’ buffer size. I can’t believe that google did communicate on the wonders of the improved latency of android jelly bean, this is really a joke.

Thread resurrection: does anyone know the status of MIDI support on Android?

I've seen mentioned that it should be supported, but I can't seem to enumerate the connected devices with the standard Juce methods. I've verified they work with other apps, e.g. USB Midi Monitor.

Are there any special permissions I need to add to the manifest? I've added this:


  <uses-feature android:name="android.hardware.usb.host"/>

Still doesn't work...

BTW it doesn't work with the Juce demo either, I get 'No MIDI Inputs Enabled' in the MIDI I/O drop-down list,  and <<NONE>> in Audio / Settings.

I'm just looking at juce_android_Midi.cpp now,  and I think I can answer my own question: Juce does not support MIDI on Android!

Though Jules seems to be saying here [1] that it does...

 

Are the limitations for different platforms listed somewhere? Could save some of us a lot of time.

 

@JPO: is your MIDI code available to see somewhere?

 

[1] http://www.juce.com/forum/topic/cross-platform-limitations

Here is the code . Remember that is was just a test, so it is very crude and incomplete, but it did work

These changes are against a juce git version from Tue Jul 23 17:38:25 2013 , but I think it should still work against juce 3

 

 

First I unpacked the kshoji driver stuff in the src directory of the android app

 

I did patch JuceAppActivity.java with this change:

--- JuceAppActivity.java.orig   2014-05-13 13:28:03.754755800 +0200
+++ JuceAppActivity.java        2014-05-13 13:30:27.740547200 +0200
@@ -53,9 +53,13 @@
 import android.media.AudioManager;
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import jp.kshoji.driver.midi.activity.AbstractMidiActivity;
+import android.util.Log;
+import android.hardware.usb.UsbManager;
+import android.content.DialogInterface.OnClickListener;

 //==============================================================================
-public final class JuceAppActivity   extends Activity
+public final class JuceAppActivity   extends AbstractMidiActivity
 {
     //==============================================================================
     static
@@ -63,6 +67,51 @@
         System.loadLibrary ("juce_jni");
     }

+    private native void midiInputCallback (int arg1, int arg2, int arg3);
+
+    @Override
+    public void onMidiMiscellaneousFunctionCodes(int cable, int byte1, int byte2, int byte3) {}
+
+    @Override
+    public void onMidiCableEvents(int cable, int byte1, int byte2, int byte3) {}
+
+    @Override
+    public void onMidiSystemCommonMessage(int cable, byte[] bytes) {}
+
+    @Override
+    public void onMidiSystemExclusive(int cable, byte[] systemExclusive) {}
+
+    @Override
+    public void onMidiNoteOff(int cable, int channel, int note, int velocity) {
+        midiInputCallback(channel + 0x80, note, velocity);
+    }
+
+    @Override
+    public void onMidiNoteOn(int cable, int channel, int note, int velocity) {
+        midiInputCallback(channel + 0x90, note, velocity);
+    }
+
+    @Override
+    public void onMidiPolyphonicAftertouch(int cable, int channel, int note, int pressure) {}
+
+    @Override
+    public void onMidiControlChange(int cable, int channel, int function, int value) {
+        midiInputCallback(channel + 0xB0, function, value);
+    }
+
+    @Override
+    public void onMidiProgramChange(int cable, int channel, int program) {}

+
+    @Override
+    public void onMidiChannelAftertouch(int cable, int channel, int pressure) {}
+
+    @Override
+    public void onMidiPitchWheel(int cable, int channel, int amount) {}
+
+    @Override
+    public void onMidiSingleByte(int cable, int byte1) {}
+
+
     @Override
     public final void onCreate (Bundle savedInstanceState)
     {

I also changed juce_android_Midi.cpp:

StringArray MidiOutput::getDevices()
{
    StringArray devices;

    return devices;
}

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

MidiOutput* MidiOutput::openDevice (int index)
{
    return nullptr;
}

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

void MidiOutput::sendMessageNow (const MidiMessage&)
{
}

//==============================================================================

struct AndroidMidiInput {
  MidiInputCallback *callback;
  MidiInput *owner;
  bool started;

  AndroidMidiInput(MidiInput *o, MidiInputCallback *cb) : callback(cb), owner(o), started(false) {
    jassert(getInstance() == 0);
    getInstance() = this;
  }
  ~AndroidMidiInput() {
    jassert(getInstance() == this);
    getInstance() = 0;
  }
  void start() { started = true; }
  void stop() { started = false; }
  void handleIncomingMidiInput(int a1, int a2, int a3) {
    if (callback) callback->handleIncomingMidiMessage(owner, MidiMessage(a1, a2, a3, 0.0));
  }

  static AndroidMidiInput *&getInstance() { static AndroidMidiInput *instance; return instance; }
};

MidiInput::MidiInput (const String& name_)
    : name (name_),
      internal (0)
{
}

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

void MidiInput::start()
{static_cast <AndroidMidiInput*> (internal)->start();
}

void MidiInput::stop()
{static_cast <AndroidMidiInput*> (internal)->stop();
}

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

StringArray MidiInput::getDevices()
{
    StringArray devs("USB Midi In");;

    return devs;
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, midiInputCallback, void, (JNIEnv* env, jobject activity,
                                                                              jint arg1, jint arg2, jint arg3))
{
  AndroidMidiInput *m = AndroidMidiInput::getInstance();
  if (m) m->handleIncomingMidiInput(arg1, arg2, arg3);
}

MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)

{
  if (index == 0) {
    MidiInput *m = new MidiInput("USB Midi In");
    m->internal = new AndroidMidiInput(m, callback);
    return m;
  }
  else return nullptr;
}

 

Nice one, thanks! I've not had a chance to try it out yet but will soon. It's certainly a good start and a great help.

Tested it and the MIDI In interface shows up. Now I just need to do MIDI out. Thanks again JPO.

Thanks to all who shared interesting info on this post.

I did try what was suggested but only got to display "USB MIDI IN" in the MIDI i/o demo (to be compared to "<None>" before the changes). Pressing keys on the connected MIDI keyboard had no effect. The keyboard works fine with other apps from the play store.

I have put the following in the manifest file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0"

          package="com.yourcompany.jucedemo">

  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:anyDensity="true"/>

  <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="19"/>

  <uses-feature android:name="android.hardware.usb.host"/>

  <uses-permission android:name="android.permission.RECORD_AUDIO"/>

  <uses-permission android:name="android.permission.INTERNET"/>

  <uses-feature android:glEsVersion="0x00020000" android:required="true"/>

  <application android:label="@string/app_name" android:icon="@drawable/icon" android:allowBackup="false" android:hardwareAccelerated="false">

    <activity android:name="JuceDemo" android:label="@string/app_name" android:configChanges="keyboardHidden|orientation">

      <intent-filter>

        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>

      </intent-filter>

      <intent-filter>

        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>

      </intent-filter>

      <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" />

      </activity>

  </application>

</manifest>

 

The res/xml/device_filter.xml file is:

<resources>

    <usb-device vendor-id="9615" product-id="2005"/>

</resources>

The values were given by the "USB Device Info" app for the same keyboard (and same Android platform). I just converted them from hex to dec.

Using latest versions of all pieces of used software.

Any hint ?

As a side question it would be great if Jules could confirm that this is the right approach for USB Midi support on Android using JUCE.

Thanks !

Sorry, I've not really been following this thread, and my knowledge of midi on android is clearly less than yours, so don't really have anything very useful to contribute here!

Ah ! ok. I was assuming that the demos you put together in JuceDemo had been tested on the usual platforms (Mac, PC, iOS, Android, Linux).

I won't blame you for not testing this particular one (MIDI i/o), as you already did so much for us, and as android is not the easiest platform to work on...

Well yes, I do test the demo app on android, of course. I just mean I've never had chance to learn about how midi could be done on android.

sorry, I don't get it.

Isn't MIDI i/o the demo that is supposed to read MIDI from an external port ? I guess my question is: "Did you manage to receive on an Android device notes or other events from some midi device (keyboard) connected via the usb port, using the MIDI i/o menu from the JUCEDEMO project ?"

Apparently several users (including myself) have troubles trying to achieve just that. Without changing anything in the code, we get "<None>" as the only choice to select a MIDI In port.

If you did manage to get it work, that means we are doing something wrong and it would be fantastic if you could tell us what you did. 

Sorry, we're talking at cross-purposes here! No, midi isn't implemented on android. The class to do it is just a stub, hence the <none>. 

(Your earlier message seemed to imply that I didn't test the demo at all on android, which is what I was trying to clarify..)

Gotcha...

smiley

@mars: assuming you started from the JuceDemo project, could you please tell us which steps you followed to actually see the MIDI device in the list (and use it, I assume ?) ? This would be very useful as I was not able to get it to work (see post #10). I am a beginner with Juce and cannot always find my way the in the Eclipse ADT/NDK maze, so please bear with me...

Thanks !

Hi Alan,

I didn't get much further than verifying that a MIDI interface shows up and that MIDI CC messages are received by my app.

I didn't try it with the JuceDemo, only my own code. I'm not an Android developer, but afaik the code from JPO only works with MIDI devices already connected when you start the app. The intents in your config I think needs to be handled somewhere by code to dynamically add a midi interface when it is connected.

Sorry I can't be of more help - will let you know if I get the time to do some more investigations.

Thank you for the response. Just one more question: when you say "my own code", do you mean a code developed under Juce or plain and simple Java code you developed directly under Eclipse? I am trying to understand if kshoji midi driver can be used easily with a Juce project. I managed to do something that works with pure Eclipse (ie java + jni/c) but not with a JUCE-generated project (yet ?...)