Unity+MIDIで音ゲーを作るならコレ！(2)

[Unity+MIDIで音ゲーを作るならコレ！(1)](https://qiita.com/ELIXIR/items/e822a6fd9663d1750a81) の続きです。


### MIDIファイルの中身を知る

前回はMIDIファイルをインポートし、再生するところまで行いました。
<iframe width="560" height="315" src="https://www.youtube.com/embed/z6S6Dtxr2ok?si=vhTU2vAjA4xAauLZ&amp;start=10" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

今回は、曲のリズムに合わせて「何か」をしてみます。

ところで、再生中のConsoleウインドウに大量のメッセージが出ているのに気が付きましたでしょうか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/d72063f8-ec36-35ed-1f24-d7c01da8bd9b.png)

MIDIファイルは、mp3のような空気の振動をデータ化したものとは違い、
* 音を鳴らす
* 音を止める
* （音色など）設定を変える

といったデータの集まりでできています。
例えば
```Note On ・・・ Note:060 Velocity:075 ・・・```
は、「ノートナンバー60を音量75で鳴らす」という情報を含んでいます。

<a href="https://fabble.cc/md-dc/scratchx-piano/note_cards/3158"><img src="https://fabble.cc/uploads/figure/content/2363/medium_MIDI-noteno.jpg"></a>

つまり、音が鳴るたびに何らかの命令が出ているので、このタイミングで何かを行うことができれば、曲に合わせて何かを行ったことになります。

### スクリプトを作成する

まずは```MIDIToObj.cs```というスクリプトを用意し、中身を下のプログラムに置き換えてください。
```MIDIToObj.cs
using MidiPlayerTK;
using System.Collections.Generic;
using UnityEngine;

public class MIDIToObj : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void OnMidiEvent(List<MPTKEvent> midievents)
    {
        foreach(MPTKEvent ev in midievents)
        {
            string msg = $"cmd:{ev.Command} tr:{ev.Track} ch:{ev.Channel} note:{ev.Value}";
            Debug.LogWarning(msg);
        }
    }
}
```

次に、Hierarchyウインドウで右クリック>Create Emptyで空のオブジェクトを作成し、わかりやすいように名前をMIDIToObjとします。
さらにこのオブジェクトに、先ほど作成したMIDIToObj.csファイルをドラッグ＆ドロップでアタッチします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/cd79215f-579e-28ad-db67-e6229ae7ddfa.png)

### イベントを登録する
「何らかのタイミングで」処理をすることをイベントと呼びます。
先ほど音を再生したMidiFilePlayerにはその機能が備わっています。

MidiFilePlayerのInspectorの真ん中あたりに、ShowUnityEventという項目があるので、左端の▲をクリックして開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/9eb7f2ed-4dc8-39f6-b9a5-630f54b67ffc.png)

次に、開いた項目の上から2番目（まんなか）の枠の右側にある+ボタンをクリックし、イベントを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/7a457aab-5553-c97f-0198-06a3905b177d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/49630fcb-d3ff-f496-5ceb-fdd257ca3906.png)

None(Object)のところにHierarchyウインドウにあるMIDIToObjをドラッグ＆ドロップします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/07ba5356-0009-e7f4-ee28-de525503d2b1.png)

さらにNo Functionのリストをクリックし、MIDITOObj > OnMidiEventを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/bc24a218-6be0-24ab-6146-29fd4c501044.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/e8ead552-342e-1840-3c1d-1c1fdd936aca.png)

ここまで出来たら再生してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/7e5b9606-729d-4e72-4725-fe1a5fa20b95.png)

Consoleウインドウに、白い吹きだしのメッセージに交じって黄色い三角のメッセージが出ていると思います。
そこでConsoleウインドウの右上の[ふきだし999+]の部分をクリックすると、三角のメッセージだけが残ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/5e8558dc-de4f-92bb-f3b5-e2008b15c3c5.png)

:::note info
ちなみに、MidiFilePlayer>Log MIDI Events Playedのチェックを外すと白い吹き出しが出なくなります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/7cfa6edb-4b69-594f-75bf-8a52912d52f0.png)
:::

### よく使われている音を探す
黄色い三角のメッセージの中で、出現頻度が高いものを探してみます。

Consoleウインドウの左から２番目の"Collapse"を押すと同じメッセージがまとまるので、頻出のコマンドがわかりやすくなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/86c0140d-412f-111e-8f20-0306c99e1b8c.png)

出現頻度が高い＝その曲でよく使われる音のタイミングで「何か」をすることで、曲に合わせて動いている感じが出やすくなるかと思います。

:::note info
ちなみに、ch:9は一般的にドラムセットに使われています。
リズムに合わせて何かを行うのであれば、```cmd:NoteOn ch:9```を探すとよいかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/003b3dea-267c-f79b-48af-66fa9cb3d5a3.png)
:::

### 曲に合わせてメッセージを出す
先ほどの```MIDIToObj.cs```を書き換えて、曲に合わせてメッセージを出してみます。

```MIDIToObj.cs
using MidiPlayerTK;
using MPTK.NAudio.Midi;
using System.Collections.Generic;
using UnityEngine;

public class MIDIToObj : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void OnMidiEvent(List<MPTKEvent> midievents)
    {
        foreach(MPTKEvent ev in midievents)
        {
            if (ev.Command == MPTKCommand.NoteOn && ev.Channel == 9)
            {
                string msg = $"cmd:{ev.Command} tr:{ev.Track} ch:{ev.Channel} note:{ev.Value}";
                Debug.LogWarning(msg);
            }
        }
    }
}
```
書き換えたのは
```diff_c
        foreach(MPTKEvent ev in midievents)
        {
+            if (ev.Command == MPTKCommand.NoteOn && ev.Channel == 9)
+            {
                string msg = $"cmd:{ev.Command} tr:{ev.Track} ch:{ev.Channel} note:{ev.Value}";
                Debug.LogWarning(msg);
+            }
        }
```
の部分です。無数に届くイベントから、```cmd:NoteOnかつch:9```のときだけメッセージを出すようにしました。

```if (ev.Command == MPTKCommand.NoteOn && ev.Channel == 9)```
の代わりに、
```if (ev.Command == MPTKCommand.NoteOn && ev.Value == 46)```
のようにすると、```cmd:NoteOnかつnote:46```の時だけメッセージが表示されるようになります(Consoleウインドウの左から２番目の"Collaspe"を解除しておかないとメッセージがまとめられてしまいます)。
<iframe width="560" height="315" src="https://www.youtube.com/embed/5U80SFoTuCk?si=22fvzmCX9Logohz0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

あとはメッセージの代わりにオブジェクトを出すだけです。ここでは割愛します。
<iframe width="560" height="315" src="https://www.youtube.com/embed/DXQMpeIWemg?si=0j8_MhAYMvev_aei" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

**音に合わせて** オブジェクトを出すことはできたのですが、
多くの音ゲーは **音が出る以前にすでにオブジェクトが出現しており、**  
音が出るタイミングでオブジェクトがボーダーラインを超えるといった必要が出てきます。
[次回](https://qiita.com/ELIXIR/items/336cd37e99b90929af40)はこちらについて行う予定です。