🥁

MIDI→OSCの送信側をUnityで作る for 電子ドラムAR

2023/12/20に公開

こんにちは、さくたまです。この記事は Iwaken Lab. アドベントカレンダーの20日目の記事です!皆さんの記事もそれぞれで面白いので見てみてください!
https://qiita.com/advent-calendar/2023/iwakenlab
私はARとドラムとNeRFが好きなXRエンジニアです。
今回はそのうちAR×ドラムの組み合わせで、電子ドラムに合わせてAR演出をした際に使ったMIDIまわりのシステムを紹介します。最後に私の今までのARドラムへのチャレンジについてまとめてますので、そちらもぜひご覧ください!

今回はMIDIを読み込んで遊んでいたプロジェクトを拡張する形だったのでUnityで作りましたが、TouchDesignerでもできるそうで、その方が一般的かもしれません。

何がしたいか

電子ドラムの演奏に合わせてARを出すために、電子ドラムのMIDI信号をOSCでiOSのARアプリ側に送信し、演出を再生したい

長年やりたいなと思いつつ...AR側の知識はついたけどMIDIや通信周りさっぱり...ということでしばらく実現せずにいたことが、OSC(Open Sound Control)を知ってできるようになりました。

今回のアプリの裏側を図示すると下図のようになります。そのうち左側部分に関して、ネットワークやMIDIの設定など、自分でも忘れがちなので備忘録的に記事化したいと思います。

使ったのは
https://github.com/keijiro/Minis
https://github.com/keijiro/OscJack
の2点です。keijiroさんいつもお世話になっております。

実装

パッケージの読み込み

Packages/manifest.json
{
  "dependencies": {
    "jp.keijiro.osc-jack": "2.0.0",
    "jp.keijiro.minis": "1.0.10",
    ...
  },
  "scopedRegistries": [
    {
      "name": "Keijiro",
      "url": "https://registry.npmjs.com",
      "scopes": [
        "jp.keijiro"
      ]
    }
  ]
}

MIDI信号を検出する

前述のMinisでMIDIのインプットを扱います。Minisは、MIDIの信号をUnityのInputSystemにおけるInputActionとして扱えるようにしてくれるpackageです。

  1. まずは「MIDIの特定の信号が来たら何かする」コードを書きます。
MIDIListener.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class MIDIListener : MonoBehaviour
{    
    [SerializeField] InputAction _action = null;
    void OnEnable()
    {
        _action.performed += OnPerformed;
        _action.Enable();
    }
    
    void OnDisable()
    {
        _action.performed -= OnPerformed;
        _action.Disable();
    }

    void OnPerformed(InputAction.CallbackContext ctx)
    {
        Debug.Log("Hit");
        Debug.Log(ctx);
    }
}
  1. GameObjectにアタッチします。

  2. Bindingを追加します。

  3. No Bindingと書いてある場所をダブルクリック

  4. Pathをクリック

    ※なぜかUIが崩れてテキスト入力フィールドとListenボタンが隠れています
    本来のUIはこちら
    公式にある本来のUI

  5. 検出したいMIDI信号を選択
    ①MIDIデバイスを接続し、Listenボタンを押した状態で検出したい信号を送って選択
    キーボードなどのデバイスの場合、ノーツで名前がついているのでわかりやすいですが、電子ドラムは対応関係がわかりづらく一つずつListenしました

②Other→MIDI Device
自分の環境ではなぜかUIが崩れてOtherを押してもポップアップしませんでした

③テキスト検索
「MIDI」と検索するとたくさん出てきます。

選択した結果がこんな感じです。

  1. 実行

OSCを送信する

https://github.com/keijiro/OscJack?tab=readme-ov-file
ドキュメントに従って、OscConnectionを作り、コンポーネントを設定して、MIDIのOnPerformedのタイミングで叩きます

  1. OscConnectionを作る
    プロジェクト内にOscConnectionを作ります。Assets > Create > ScriptableObjects > OSC Jack > Connectionで作成できます。
    接続したい先(今回はiPhone)のプライベートIPアドレスを確認します

    確認したIPアドレスをOscConnectionに設定します。

  2. コンポーネントの設定
    OSCJackには、OscEventReceiverとOscPropertySenderというコンポーネントが用意されています。
    OscEventReceiver...任意のUnityEventが発生した時に任意の値を送信するコンポーネント
    OscPropertySender...オブジェクトのプロパティを監視して送信してくれるコンポーネント

今回は、値の監視というよりは、Hitのタイミングで値を送信したいので、OSCPropertySenderから、OscClientを叩くところを抜き出して以下のようなOscSenderを書きました。

OscSender.cs
using UnityEngine;
using OscJack;

public class OSCSender : MonoBehaviour
{
    #region Editable fields

    [SerializeField] OscConnection _connection = null;
    [SerializeField] string _oscAddress = "/unity";

    #endregion

    #region Internal members

    OscClient _client;

    void UpdateSettings()
    {
        if (_connection != null)
            _client = OscMaster.GetSharedClient(_connection.host, _connection.port);
        else
            _client = null;
    }

    #endregion

    #region MonoBehaviour implementation

    void Start()
    {
        UpdateSettings();
    }

    void OnValidate()
    {
        if (Application.isPlaying) UpdateSettings();
    }

    #endregion

    public void Send(float data)
    {
        _client.Send(_oscAddress, data);
    }
}
  1. OscSenderにConnectionとAddressを設定
    先ほど作成したConnectionと、任意のAddressを設定します。OSCでは、受信側のアプリケーションが受け取る情報をアドレスという形で分けて管理することができます。OSCを受け取る側の実装に合わせて設定しますが、今回はデフォルトのままです。

  2. 先ほどのMIDIListener.csのOnPerformed内でOscSenderのSendを呼びます。
    InputActionにスペースキーなど入れておいてOSCだけテストできるといいですね。

MIDIListener.cs
    void OnPerformed(InputAction.CallbackContext ctx)
    {
        Debug.Log("Hit");
        Debug.Log(ctx);
        _sender.Send(1.0f);
    }

このようにiPhoneにOSCが送信できていることがわかりました。今回はProtokolというアプリを使って確認しました。
https://apps.apple.com/jp/app/protokol-midi-osc-monitor/id1451570764

MIDIパッドに対応して信号を出しわけ

最終的に、MIDIのパッドごとの出しわけをするためには、ctx.controlMidiNote:/MidiDevice/note057のようなデータが入っているので、今回はその最後の番号をidとして扱っています。

MIDIListener.cs
    void OnPerformed(InputAction.CallbackContext ctx) {
        if (_sender != null)
        {
            int id = ExtractNumber(ctx.control.ToString());

            _sender.Send((int)id);
        }
        Debug.Log("Hit");
    }
    
    public static int ExtractNumber(string input)
    {
        Match match = Regex.Match(input, @"\d+");

        if (match.Success)
        {
            int number = Convert.ToInt32(match.Value);
            return number;
        }
        else
        {
            return 0;
        }
    }

電子ドラムを叩くとログが出るようになってます。現実の叩いている映像なくてすみません。
送信側

受信側

出来上がったもの

ARアプリ側にVPSを実装し、MIDIの位置とエフェクトの位置を対応づけて、このようなエフェクトを作ることができました

さくたまARドラムの振り返り

2022/2/15: MIDIのインプット可視化アプリ

Minisはこの時に導入方法を学びました。

2022/6/10: Immersalでドラム位置合わせ

ここで、MIDIと繋げよう!と思ったら、「あれ...?AR iPhoneでしかできないからiPhoneにMIDIを入れないといけない?」と思い...しばらく立ち止まっていました。

2023/6/15: 屋外でVPSライブ

https://x.com/sakutama_11/status/1674777397428895744?s=20
自分の生演奏にARエフェクトをつけるということで...とりあえずやった!という点では良かったと思います

位置合わせ...Geospatial API+ボタン微調整
タイミング合わせ...ボタンで人力
エフェクト...Particle System

タイミングと位置合わせがだいぶ渋かったですし、Particle Systemがうまく扱えず、パルス感のある表現ができなかったなと思います。
また、生ドラム&生演奏だとオーディオリアクティブの難易度も上がるなと思いました。

2023/10/31: MIDI→OSCで通信&VFX Graph


ARドラムについてずっとやりたかったことの一通りのやり方がやっと見えました
これから表現などもっと作って、いつかライブとして演奏してみたいと思います!

おわりに

読んでくださってありがとうございました!
Iwaken Lab. アドベントカレンダーの他の記事もぜひ見て見てください!

Discussion