Android MIDI Over BLE をやってみた

iOS には前から Core MIDI ってのがあって、MIDIの入出力をアプリからAPIで利用することができたんだけど、
Android は、6.0(SDK 23)から [Android MIDI](http://developer.android.com/intl/ja/reference/android/media/midi/package-summary.html) ってのができたらしいです。

いままで Android で MIDI を使うために [Kaoru Shoji](https://github.com/kshoji) さんとか、いろいろと頑張ってるひとがいたんだけど、それがやっと OSレベルで対応されたってことです。

そして、Android MIDI では [Apple の BLE MIDI 規格](https://developer.apple.com/bluetooth/Apple-Bluetooth-Low-Energy-MIDI-Specification.pdf)も使えるそうなので、それをやってみます。

Apple の BLE MIDI 規格が使えるってことは、Macとつなぐこともできるってことなんだけど、せっかくだから今回は以前作った[コレ](https://www.facebook.com/wdmd01/)を使いました。
![IMG_0522_1.JPG](https://qiita-image-store.s3.amazonaws.com/0/8050/920fabce-d59b-0722-e802-a05ae8d98a1e.jpeg)
コレは何か簡単に説明すると、自作のMIDIコントローラーです。
８の字に空いてるところがタッチセンサーになっていて、ワニ口クリップで野菜とかバナナとかと繋いで楽器にしてしまいます。
それを BLE MIDI で iPad に送って、GarageBand で音を鳴らして遊んでたんだけど、この話はあんまり関係ないのでこのくらいにしときます。


## BluetoothDevice を Scan する

まず、Android 6.0 から Ble の Scan に ACCESS_COARSE_LOCATION か ACCESS_FINE_LOCATION が必要です。

```xml
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
```

リクエストします。

```java
if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
    requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
```
Scan して、取得した BluetoothDevice を保存します。

```java 
static final UUID MIDI_SERVICE_UUID = UUID.fromString("03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
BluetoothDevice mDevice = null;

void scanStart() {
    ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
    ScanFilter uuidFilter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(MIDI_SERVICE_UUID)).build();
    List<ScanFilter> scanFilters = new ArrayList<>();
    scanFilters.add(uuidFilter);
    BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().startScan(scanFilters, settings, mScanCallback);
}

void scanStop() {
    BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().stopScan(mScanCallback);
}

ScanCallback mScanCallback =
    new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            mDevice = result.getDevice();
            scanStop();
        }
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            if(0 < results.size()){
                mDevice = results.get(0).getDevice();
            }
            scanStop();
        }
        @Override
        public void onScanFailed(int errorCode) {
            scanStop();
        }
    };

    
```

## BluetoothDevice と接続する

BluetoothDevice を MIDI デバイスとして接続します。

```java
MidiManager m = (MidiManager) getSystemService(Context.MIDI_SERVICE);
m.openBluetoothDevice(mDevice, new MidiManager.OnDeviceOpenedListener() {
    @Override
    public void onDeviceOpened(MidiDevice device) {
        if(0 < device.getInfo().getOutputPortCount()){
            MidiOutputPort outputPort = device.openOutputPort(0);
            outputPort.connect(new MyReceiver());
        }
    }
}, new Handler(Looper.getMainLooper()));

```

MyReceiver で受信します。
※とりあえず ノートオン・ノートオフだけ。

```java
class MyReceiver extends MidiReceiver {
    @Override
    public void onSend(byte[] data, int offset,
                       int count, long timestamp) throws IOException {
        int i = offset;
        byte message = (byte) (data[i++] & 0xF0);
        byte noteNo = data[i++];
        byte velocity = data[i];
        if (message == (byte)0x80 || ((message == (byte)0x90 && velocity == 0))) {
            Log.d(TAG, String.format("noteOff: 0x%02x", noteNo));
        }else if (message == (byte)0x90) {
            Log.d(TAG, String.format("noteOn: 0x%02x, velocity: 0x%02x", noteNo, velocity));
        }
    }
}
```

今回はやってませんが、送信もできるそうです。
[Send a NoteOn - Android MIDI User Guide](http://developer.android.com/intl/ja/reference/android/media/midi/package-summary.html#send_a_noteon)
## 他のアプリから使う
iOSの場合、Core MIDI に対応してるけど、BLE MIDI には対応してないアプリ（[Music Studio](https://itunes.apple.com/jp/app/music-studio/id328608539?mt=8)とか）でも、GarageBand などで BLE MIDI 接続してしまえば、BLE とか関係なしに MIDI デバイスとして使えるようになります。

これが Android MIDI でもできるのか、試してみました。

## BLE を使わない Android MIDI アプリをつくる

BLE を使わないので Permission は不要です。
BLE を使うと、音楽アプリなのに位置情報の許可が必要とか、ユーザーにとってわけわかんないことになってしまうので、結構重要なことです。

```xml
<!-- Permission 不要 -->
```

ボタンクリックしたら、接続されている MIDI デバイスの一覧を取ってきて、1個めに接続します。

```java
@Override
public void onClick(View v) {
    MidiManager m = (MidiManager) getSystemService(Context.MIDI_SERVICE);
    MidiDeviceInfo[] devices = m.getDevices();
    Log.d(TAG, "devices: " + Arrays.toString(devices));
    if(0 < devices.length){
        m.openDevice(devices[0], new MidiManager.OnDeviceOpenedListener() {
            @Override
            public void onDeviceOpened(MidiDevice device) {
                if(0 < device.getInfo().getOutputPortCount()){
                    MidiOutputPort outputPort = device.openOutputPort(0);
                    outputPort.connect(new MyReceiver());
                }
            }
        }, new Handler(Looper.getMainLooper()));
    }
}
```

MyReceiver はさっきと一緒です。

BLE対応アプリで接続した後に、BLE非対応アプリで MIDI デバイス一覧を取得すると、次のように BluetoothDevice も含んでます。
※ Log 出力結果です。

```
devices: [MidiDeviceInfo[mType=3,mInputPortCount=1,mOutputPortCount=1,mProperties=Bundle[{name=WDMD01, bluetooth_device=C6:0E:FC:01:05:87}],mIsPrivate=false]
```

このあと、BLE MIDI デバイスから、ノートオン・ノートオフを送ったら、BLE非対応アプリでもちゃんと受信できました。

さらに、BLE対応アプリの方を終了しても、接続は生きてるようで、BLE非対応アプリでまだ受信できます。

これだったら GarageBand のような、標準となるアプリだけ BLE MIDI 対応してしまえば、他のアプリは対応不要になるのでいいですね。

とはいっても Android には、GarageBand がないので、Music Studio の Android版とか期待してます。

## まとめ
Android MIDI は iOS の Core MIDI と同じように使える。

あ、それから
BLEには対応してませんが、Android MIDI API の公式サンプルが出たらしいです。
[新しい Android Marshmallow (マシュマロ) のサンプル アプリ - Google Developer Japan Blog](http://googledevjp.blogspot.jp/2015/12/android-marshmallow.html)