2012年11月2日金曜日

Android USB MIDI Driverのご紹介

Android USB MIDI Driverって?

「Android 3.1 (API Level 12)以降向けの、USB MIDIを使うためのライブラリ」です。
Android 3系のタブレットや、Android4系のスマートフォンを使って、特に改造やroot化することなしに、USB MIDI対応の楽器や機器を繋げることができるようになります。

注意:端末がUSB Host機能に対応している必要があります。USB Host対応端末であっても、USB MIDI機器が認識しない場合があります。

USB MIDI対応デバイスの例:キーボード、電子ピアノ、シンセサイザー、音源モジュールなどの楽器・コントローラ、変換ケーブルなどのPC用MIDIインタフェースなど。

ライブラリのソースコードは、こちらのgithubリポジトリからダウンロードできます。Apache ライセンス バージョン 2.0です。
https://github.com/kshoji/USB-MIDI-Driver

応用アプリの例

  • (楽器のほうの)キーボードを使ってプレゼンを操作
  • 演奏をディスプレイ上にリアルタイムで可視化
  • TENORI-ON/monomeのようなインタラクティブなシーケンサー
  • 加速度やジャイロなどのセンサーを使用したコントローラ
などなど…

まずはサンプルを試してみる

サンプルプロジェクトをそのままコンパイルしたものを、Google Playマーケットにて公開しています。手っ取り早く試してみたい方は、そちらからインストールしてみてください。

まずはサンプルプロジェクトをビルドして実行してみましょう。
githubからzipとしてダウンロード、もしくは、git clone git://github.com/kshoji/USB-MIDI-Driver.git としてcloneします。完了したら、Eclipseのメニューから「Import→Existing Projects into Workspace」を実行します。

これらのプロジェクトはmavenプロジェクトになっていますが、mavenが導入されていない場合でも正しくビルドされます。Javaコンパイラの設定が「1.5」だとコンパイルが通らないので「1.6」にしてください。

無事ビルドが終わったら、「MIDIDriverSample」プロジェクトを右クリックして、Run As→Android Applicationで端末にインストールできます。対応するAndroid端末が手元にない場合は、Android-x86の仮想マシンを使うことで試せます。Android SDK付属のエミュレータではUSB Hostの機能は使えません。

サンプルプロジェクトをインストールすると、ランチャーにアプリのアイコンが2つ表示されます。「MIDIDriverSample (Single Device)」と「MIDIDriverSample (Multi Device)」です。前者はデバイスを1つだけ、後者は複数のデバイスを接続可能で、それ以外の機能は同じです。
アプリ起動中にUSB MIDIの機器を接続するとシステムダイアログが表示され、承認することでアプリに表示されます。

MIDIDriverSample スクリーンショット

このサンプルプロジェクトはUSB MIDIのイベントログを表示するもので、ライブラリの簡単な動作テストが出来るようになっています。例えば、キーボードを繋いだ場合には、鍵盤を押すとノートオン・オフのメッセージが流れ、音が鳴ります。画面上の鍵盤をタップして、接続されている楽器を演奏することもできます。

サンプルプロジェクトのサウンド合成処理を全てJava上(AudioTrack)で行っている関係上、機種に依存しますが少なくとも100ミリ秒のタイムラグがあります。イベント処理自体は、実際には気にならない程度の処理速度になっています。

API Level 17で遅延の少ないデバイスがサポートされたようですが、ストリーミングのAudioTrackは遅いままです。楽器やゲームなどの用途のために、イベント受信→発音までのタイムラグをなるべく小さくしたい場合には、NDKでOpenSL ESを使って音声を合成してやる必要があります。

ライブラリの使い方

複数デバイス対応を後から入れたため、二つのサンプルコードActivityが存在しています。
サンプルプロジェクトのMIDIDriverSingleSampleActivity、もしくは、MIDIDriverMultipleSampleActivityのコードを追うとライブラリの使い方がだいたいわかるようになっています。

まずは「AbstractSingleMidiActivityクラス、もしくは、AbstractMultipleMidiActivityクラス を継承したActivityクラス」を作成します。このActivityが起動されると自動的にデバイス接続の待ち受けを行います。
受信した「MIDIメッセージ」が全てActivityへ通知されます。
メッセージの送信も、同じイベントリスナを実装したクラスから、受信と同じような手順で実行できます。

Androidを楽器(受信側)として使用する場合

「AbstractSingle(Multiple)MidiActivityクラスを継承したActivityクラス」には、未実装のonMidi***系の名前のメソッドがあるので、この実装に、それぞれのメッセージを受信したときに処理したい内容を記述します。

例えば、発音のタイミングで何かを処理したい場合、以下のような感じになります。
@Override
public void onMidiNoteOn(final MidiInputDevice sender, int cable, int channel, int note, int velocity) {
    // TODO ID:cableの機器、MIDIチャンネル:channel、音程:note、強さ:velocity の音を鳴らす処理
}

Androidを楽器のコントローラーとして使用する場合

単一デバイスのみ対応の場合


  1. AbstractSingleMidiActivity#getMidiOutputDevice()を呼ぶと、MIDI出力用のデバイスオブジェクト(MidiOutputDevice)が取得できます。
  2. MidiOutputDeviceのメソッド(sendMidi***系の名前のメソッド)を呼んでやると、接続している楽器に対してMIDIメッセージが送信できます。

複数デバイス対応の場合


  1. AbstractMultipleMidiActivity#getConnectedUsbDevices()を呼ぶと、接続されている全てのUSB MIDI機器の情報オブジェクト(UsbDevice)が取得できます。
  2. 次に、AbstractMultipleMidiActivity#getMidiOutputDevices(UsbDevice)を呼ぶと、そのデバイスと関連する全てのMIDI出力用のデバイスオブジェクト(Set)が取得できます。
  3. MidiOutputDeviceのメソッド(sendMidi***系の名前のメソッド)を呼んでやると、接続している楽器に対してMIDIメッセージが送信できます。
なお、getMidiOutputDevice()はnullを返す場合があるので、事前にチェックしてください。

Androidから、接続されている楽器の音色を変更する場合は、以下のような感じになります。
MidiOutputDevice device = getMidiOutputDevice();
if (device != null) {
    device.sendMidiProgramChange(cable, channel, program);
}

もっと詳しく:APIの一覧

実際にActivity内で使われるのは、ほぼOnMidiEventListenerとMIDIOutputDeviceのメソッドになりますので、このインターフェースに含まれるメソッドの解説です。
MIDI周りに詳しい人だと、メソッド名で大体想像が付くかと思います。
仕様に定義されているMIDIメッセージを全部実装しているので、あまり使われないものも含まれています。

全て、戻り値がvoidのメソッドです。
MIDIOutputDeviceではメソッド名が、「on」の代わりに「send」になっています。
  • onMidiMiscellaneousFunctionCodes
    • その他の機能の設定を行います。将来の拡張用に予約されています。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int byte1:1バイト目
    • 引数 int byte2:2バイト目
    • 引数 int byte3:3バイト目
  • onMidiCableEvents
    • 「ケーブル」単位の設定を行います。将来の拡張用に予約されています。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int byte1:1バイト目
    • 引数 int byte2:2バイト目
    • 引数 int byte3:3バイト目
  • onMidiSystemCommonMessage
    • MIDI機器全体の設定を行います。「1バイト」のシステムエクスクルーシブもこのメソッドで扱います。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 byte[] bytes:長さは1〜3バイト
  • onMidiSystemExclusive
    • MIDI機器全体の設定を行います。任意バイトサイズのバイナリを通信します。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 byte[] systemExclusive、任意の長さ
  • onMidiNoteOff
    • 特定チャンネルの特定の音(ノートナンバー)の発音を停止します。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int channel:チャンネルID(0〜15)
    • 引数 int note:ノートナンバー(0〜127、中央のドが60)
    • 引数 int velocity:強さ(0〜127)
  • onMidiNoteOn
    • 特定チャンネルの特定の音(ノートナンバー)を発音します。velocityが0の場合は発音を停止します。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int channel:チャンネルID(0〜15)
    • 引数 int note:ノートナンバー(0〜127、中央のドが60)
    • 引数 int velocity:強さ(0〜127)
  • onMidiPolyphonicAftertouch
    • 鍵盤を押し下げたあと、さらに鍵盤を押し込むことによるコントロール「アフタータッチ」の効果を指定します。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int channel:チャンネルID(0〜15)
    • 引数 int note:ノートナンバー(0〜127、中央のドが60)
    • 引数 int pressure:アフタータッチの圧力(0〜127)
  • onMidiControlChange
    • チャンネルの音色コントローラのパラメータを変更するのに使います。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int channel:チャンネルID(0〜15)
    • 引数 int function:コントローラの番号(0〜127)
    • 引数 int value:パラメータの値(0〜127)
  • onMidiProgramChange
    • チャンネルの音色を変更します。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int channel:チャンネルID(0〜15)
    • 引数 int program:音色番号(0〜127)
  • onMidiChannelAftertouch
    • チャンネル全体に対する「アフタータッチ」効果を指定します。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int channel:チャンネルID(0〜15)
    • 引数 int pressure:アフタータッチの圧力(0〜127)
  • onMidiPitchWheel
    • チャンネルに対する「ピッチベンド」の効果を指定します。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int channel:チャンネルID(0〜15)
    • 引数 int amount:0(低い)〜8192(中央)〜16383(高い)
  • onMidiSingleByte
    • 特別なケースで、1バイトずつのMIDIイベントを「解釈させずに」やりとりする際に使用される。
    • 引数 MidiInputDevice sender:イベントを送信したデバイス
    • 引数 int cable:ケーブル識別ID(0〜15)
    • 引数 int byte1