Android MIDI Over BLE をやってみた

iOS には前から Core MIDI ってのがあって、MIDIの入出力をアプリからAPIで利用することができたんだけど、
Android は、6.0(SDK 23)から Android MIDI ってのができたらしいです。

いままで Android で MIDI を使うために Kaoru Shoji さんとか、いろいろと頑張ってるひとがいたんだけど、それがやっと OSレベルで対応されたってことです。

そして、Android MIDI では Apple の BLE MIDI 規格も使えるそうなので、それをやってみます。

Apple の BLE MIDI 規格が使えるってことは、Macとつなぐこともできるってことなんだけど、せっかくだから今回は以前作ったコレを使いました。
wdmi01
コレは何か簡単に説明すると、自作のMIDIコントローラーです。
8の字に空いてるところがタッチセンサーになっていて、ワニ口クリップで野菜とかバナナとかと繋いで楽器にしてしまいます。
それを BLE MIDI で iPad に送って、GarageBand で音を鳴らして遊んでたんだけど、この話はあんまり関係ないのでこのくらいにしときます。

BluetoothDevice を Scan する

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

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

リクエストします。

if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
    requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}

Scan して、取得した BluetoothDevice を保存します。

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 デバイスとして接続します。

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 で受信します。
※とりあえず ノートオン・ノートオフだけ。

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

他のアプリから使う

iOSの場合、Core MIDI に対応してるけど、BLE MIDI には対応してないアプリ(Music Studioとか)でも、GarageBand などで BLE MIDI 接続してしまえば、BLE とか関係なしに MIDI デバイスとして使えるようになります。

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

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

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

<!-- Permission 不要 -->

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

@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

nRF51でBLE MIDIをアドバタイズする

BLE MIDI ってのは、Bluetooth SIG で決まってるわけじゃないのでいくつか仕様があるのですが、
今回は iOS の GarageBand と繋げたいので、Apple の BLE MIDI を使います。

仕様はこちら、
Apple Bluetooth Low Energy MIDI Specification

その仕様によると BLE MIDI の UUID は、

Name UUID
Service 03B80E5A-EDE8-4B33-A751-6CE34EC4C700
I/O Characteristic 7772E5DB-3868-4112-A1A9-F2669D106BF3

ここまでわかれば、SDK にサンプルとして入ってる ble_nus ( Nordic UART Service ) を、
ちょちょっといじればアドバタイズまではできるはずです。

そして ble_nus の UUID はこちら。

Name UUID
Service 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
TX Characteristic 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
RX Characteristic 6E400003-B5A3-F393-E0A9-E50E24DCCA9E

ble_nus.c によると、Service 追加の方法は、

uint8_t         uuid_type;
ble_uuid128_t   nus_base_uuid = {{0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
                                  0x93, 0xF3, 0xA3, 0xB5, 0x00, 0x00, 0x40, 0x6E}};
ble_uuid_t      ble_uuid;
uint16_t        service_handle;

// Add custom base UUID.
sd_ble_uuid_vs_add(&nus_base_uuid, &uuid_type);

ble_uuid.type = uuid_type;
ble_uuid.uuid = BLE_UUID_NUS_SERVICE; // 0x0001

// Add service.
sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                         &ble_uuid,
                         &service_handle);

Characteristic は、

ble_gatts_char_md_t      char_md;
ble_gatts_attr_t         attr_char_value;
ble_uuid_t               char_uuid;
ble_gatts_attr_md_t      attr_md;
ble_gatts_char_handles_t rx_handles;

// 省略するけど char_md と attr_char_value をいろいろ設定

char_uuid.type = uuid_type;
char_uuid.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC; // 0x0002;

// Add RX Characteristic.
sd_ble_gatts_characteristic_add(service_handle,
                                &char_md,
                                &attr_char_value,
                                &rx_handles);

やり方がわかったので、
これを BLE MIDI に置き換えてみます。

まず、Base UUID から
Service UUID の一部を 0 変えたら、
03B80000-EDE8-4B33-A751-6CE34EC4C700 になるので、前後ひっくり返して…

ble_uuid128_t   midi_base_uuid = {{0x00, 0xC7, 0xC4, 0x4E, 0xE3, 0x6C, 0x51, 0xA7,
                                   0x33, 0x4B, 0xE8, 0xED, 0x00, 0x00, 0xB8, 0x03}};

次に、Service
さっき 0 に変えたところを uuid として指定します。

ble_uuid.type = uuid_type;
ble_uuid.uuid = 0x0E58;

// Add service.
sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                         &ble_uuid,
                         &service_handle);

あとは、Characteristic なんだけど、
Service と Base UUID が違う。

ここで、かなりはまったんだけど。
結局、Base UUID をもう一個作ったらうまくいきました。

uint8_t         char_uuid_type;
ble_uuid128_t   midi_char_uuid = {{0xF3, 0x6B, 0x10, 0x9D, 0x66, 0xF2, 0xA9, 0xA1,
                                   0x12, 0x41, 0x68, 0x38, 0x00, 0x00, 0x72, 0x77}};
ble_uuid_t      char_uuid;

// Add custom base UUID.
sd_ble_uuid_vs_add(&midi_char_uuid, &char_uuid_type);

char_uuid.type = char_uuid_type;
char_uuid.uuid = 0xE5DB;

よかった。
ここまでできたら GarageBand と接続できます。
あとは、Appleの仕様とMIDIの仕様のとおりにノート送ったりするだけなので簡単です。

なんか作ったよ

BLE MIDI を使って、野菜を楽器にして遊ぶデバイスを作ってみました。
Maker Faire に出したら、ちびっこたちに大人気。
せっかくの無線なのにケーブルいっぱいですみません。

wdmi01.jpg

Google Apps Script ウェブアプリケーション の作り方

Contact Form 7 から Google Drive のスプレッドシートに書き込むプラグインで必要な GoogleAppsScriptウェブアプリケーション の作り方です。

  1. スプレッドシートを作る

  2. スクリプトエディタを開く

    「ツール」→ “Script editor” を選びます。

  3. スプレッドシートに自動書き込みするスクリプトを貼り付ける

    デフォルトで入っている function myFunction() {} というやつは消して、ここにあるスクリプトをコピー&ペーストしてください。https://gist.github.com/miyakeryo/9383548

  4. スクリプトを保存する

    プロジェクト名は “無題のプロジェクト” でも何でもいいので保存します。

  5. setUpを実行する

    「実行」→ “setUp” を選びます。
    実行権限の承認を求められるので承認します。

  6. バージョンを保存する

    「ファイル」→ “版を管理…” を選び、新しいバージョンを保存します。

  7. Webアプリケーションとして公開する

    「公開」→ “ウェブアプリケーションとして導入…” を選びます。
    “次のユーザーとしてアプリケーションを実行” が “自分” になっていることを確認します。
    “アプリケーションにアクセスできるユーザー” を “全員(匿名ユーザーを含む)” に変更します。
    “導入” ボタンをクリックします。
    “ウェブアプリケーションのURL” が表示されます。
    以上で Google Apps Script ウェブアプリケーションができました。このURLをコピーしておいてください。