コポうぇぶろぐ

コポコポによる備忘録&Tipsブログ

【Unity】MidiJackをAndroidで動かしてみた(検証:2022.06.12)

Androidで動作するUnityアプリでMIDIキーボードの入力を取得する方法として、MidiJack(Keijiro神 作)を使った方法を紹介します。
こちらはプルリク(momo-the-monsterさん)にてAndroidの対応をしている方がいらっしゃいましたので、その導入について調査した備忘録になります。

こちらのプルリクは master にマージされておりませんので、MidiJackとは非公式な手段となります。
そして結論といたしまして…、残念ながらAndroidでのUSE MIDI接続の動作は不安定でした。(BLEは大丈夫そう?)

!!注意!!
今回の記事は、見方によってはちょっとディスっぽい記事に見えてしまうかもですが、MidiJackはかなり素晴らしいモジュールです!
私が勝手に他の人のAndroid対応に期待し、そしてダメだったという記事になっています。
同じようにAndroid対応されているの!?と期待した人向けに、現状はダメっぽいという報告のようなものです。

MidiJackとは

KeijiroさんのGithubにて公開されている、MIDI機械の入力を取得するモジュールです。
github.com

Minisという新InputSystemを用いたモジュールもあるようでしたので、合わせてご紹介いたします(今回は使いません)。
github.com

MidiJack(Android対応版)の導入方法

こちらの GitHub の Pull Requests にアクセスします。
こちらに「Android Support (enhancement)」というリクエストがあるのでアクセスします。
Android Support by momo-the-monster · Pull Request #19 · keijiro/MidiJack · GitHub

UnityPackageのダウンロード

ページ上部にある「File changed」にアクセスします。ここで変更されたファイル一覧が表示されるので、検索欄に「unitypackage」と入力すると「MidiJack.unitypackage」があるので、これをダウンロードします。ダウンロードは三点メニューから「View file」をクリックするとダウンロードできます。
https://github.com/keijiro/MidiJack/blob/9908e6e29e7b9f9aaa09a6bbfc49d2234f0295c4/MidiJack.unitypackage

UnityPackageのインポート

ダウンロードしたパッケージをプロジェクトにインポートします。
この時点でAndroidビルドを行ってもエラーが発生しますので、次の対応をしていきます。

AndroidManifest.xmlの編集

「MidiJackのAndroidManifestにある、タグの"android:label"が重複しています!」というエラーが発生していたので、これに対応していきます。といってもエラー内容に対策方法が記述されていたので、その対策を施していくだけです。

まず正規のAndroidManifestをゲットします。こちらはUnityのインストール先に大元があります。

C:\Program Files\Unity\Hub\Editor\(バージョン)\Editor\Data\PlaybackEngines\AndroidPlayer\Apk
UnityManifest.xml

このUnityManifest.xmlをプロジェクトにコピーおよびリネームします。コピー先は次の場所です。

Assets/Plugins/Android/AndroidManifest.xml

絶対にここに置いてください。

では内容を編集していきます。編集箇所は<application … >の箇所です。

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.unity3d.player"
    xmlns:tools="http://schemas.android.com/tools">
    <application android:label="@string/app_name"
                         tools:replace="android:label">
        <activity android:name="com.unity3d.player.UnityPlayerActivity"
                  android:theme="@style/UnityThemeSelector">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
        </activity>
    </application>
</manifest>

<application に続いて、「android:label="@string/app_name"」というUnity側が置換する箇所をそのままにしておき、「tools:replace="android:label"」でこのマニフェストの優先度を上げました。これでひとまずビルドが通るようになりました。

MidiDroidCallbackクラスの編集

最後に入力不具合を取っていきます。現状では MidiDroidCallbackクラスにて「No such proxy method」といったエラーが発生します(SRDebugger等で確認)。このエラーは AndroidJavaProxy クラスにて該当するメソッドが存在しなかった場合に例外をスローします。結論から言うと、次のように対策します。

using UnityEngine;

namespace MidiJack
{
    public class MidiDroidCallback: AndroidJavaProxy
    {
        public delegate void RawMidiDelegate(object sender, MidiMessage m);
        public event RawMidiDelegate DroidMidiEvent;

        public MidiDroidCallback() : base("mmmlabs.com.mididroid.MidiCallback") { }

        // 追加するメソッド
        public override AndroidJavaObject Invoke(string methodName, object[] args)
        {
            var deviceIndex = (int)args[0]; // Type = Int32
            var status = (sbyte)args[1];    // Type = SByte(端末依存?)
            var data1 = (sbyte)args[2];     // Type = SByte
            var data2 = (sbyte)args[3];     // Type = SByte

            //Debug.Log($"COPO: {methodName}, devID={deviceIndex}, status={status}, d1={data1}, d2={data2}");

            midiJackMessage(deviceIndex, (byte)status, (byte)data1, (byte)data2);
            return null;
        }

        public void midiJackMessage(int deviceIndex, byte status, byte data1, byte data2)
        {
            if (DroidMidiEvent != null)
            {
                DroidMidiEvent(this, new MidiMessage((uint)deviceIndex, status, data1, data2));
            }
        }
    }
}

AndroidJavaProxyはInvoke関数内で、クラス内に定義されたメソッドをリフレクションを使って調査しています。この箇所は結構問題があるようで、なかなか該当するメソッドを見つけてくれないみたいです。なのでInvokeをオーバーライドしてリフレクションを使わず、直に引数をキャストする等しています。


以上でAndroid上で動作するはずです!

動作不安定について

この導入方法はプルリクエスト(数年前のもの)にあったものを無理くり使ったわけなのですが、私の端末(Galaxy S9+)では高速にMIDI入力を行うとアプリがクラッシュします。なので、AndroidでUSE MIDI入力を検知する方法については別の手段を用いたほうが良さそうです。

現在は USB Midi Android Plugin を試しています。これまた無償で提供されており大変ありがたいです。
⇒残念ながらこちらも動作不安定でした。Androidの場合、有償のアセットを利用するか、独自にライブラリを作成するしか方法がなさそう?です。
assetstore.unity.com