MIDI再生
このサンプルでは、MIDIから作成されたオーディオファイルと同期してMIDIファイルを再生します。これを利用することで、MIDIトラックからワールド内のイベントを同期させ、音楽とビジュアルを結びつけて没入感のある体験を作り出すことができます。
Midi Playback Example Worldにアクセスして、実際に試してみてください!
サンプルの使用方法
Unity Editorでシーンを再生するか、VRChatでワールドにアクセスしてMIDI再生を確認してください。短いループが再生されると、音楽に合わせて異なる色の画像がフラッシュします。これらはMIDIファイル内の4つの異なるチャンネルで発生するMIDIノートイベントを視覚化したものです。
サンプルのインポート
以下の手順に従って、このサンプルをUnityプロジェクトに追加してください:
- Unity Editorのメニューから「VRChat SDK > 🏠 Example Central」を選択し、Example Centralウィンドウを開きます。
- リストの中からこのプレハブを探すか、タイトル(このページのタイトルと同じ)で検索してください。
- 「Import」ボタンを押して、Unitypackageをプロジェクトにインポートします。
技術的な解説
VRCMidiPlayerはAudioSourceと似ていますが、代わりにMidi Assetを使用し、Note OnおよびNote OffイベントをMidiGrid UdonBehaviourへ送信します。
MidiGrid
このプログラムは、MidiのNote OnおよびOffイベントを色付きのブロックとして視覚化します。
VRCMidiPlayerからNote Onイベントが送信されるたびに、プログラムはそのチャンネルがグリッド(以下のchannelsフィールドを参照)のいずれかと一致するかを確認します。一致する場合、プログラムはノート番号を12で割った余りを計算して対応する画像を見つけ、該当するブロックを表示させます。例えば、ノート番号11と12はグリッド内の11番目と12番目の画像を表示し、13と14は0番目と1番目の画像を表示するように、ノート番号をグリッドに合わせて循環させます。対象の画像が計算されると、それが表示状態になります。
VRCMidiPlayerからNote Offイベントが送信されると、プログラムは同じ計算を行って対象のグリッドと画像を見つけますが、非表示にするためにその画像を無効化します。
インスペクターのフィールド
| 名前 | 説明 |
|---|---|
grids | RectTransformを参照しており、その配下にGridLayoutGroupと、1オクターブ分の音符に対応する12個の子Imageが含まれています。 |
channels | MIDIチャンネルをシーン内の4つの画像グリッドに再割り当てするために使用する整数の配列です。デフォルト値の [3, 4, 1, 2] では、チャンネル3のノートイベントがグリッド0に、チャンネル4のイベントがグリッド1に表示されます(以降同様)。 |
player | MidiGridにイベントを送信するVRCMidiPlayerへの参照です。 |
データの入れ替え
MIDIファイルやオーディオファイルがある場合は、それらをインポートして既存のアセットと置き換えることで、シーン内でどのように表示されるかを確認できます。
チャンネルの変更
MidiGridプログラムにはchannelsという変数があります。この配列は、MIDIデータのチャンネルと画面上のグリッドを対応させるものです。デフォルトの順序は「3 4 1 2」となっており、これは1番目のグリッドがチャンネル3のデータを、2番目のグリッドがチャンネル4のデータを表示することを意味します。ここの順序を入れ替えることで、表示を少し変更できます。
独自のMIDIデータファイルを読み込む場合、Unityのコンソールを確認することで、再生されているチャンネルとノートを確認できます。Note Onイベントが発生するたびに、「3:75」のようなメッセージがログに記録されます。これは、チャンネル3でノート75が再生されたことを示しています。
グリッドの追加
4つ以上のチャンネルを持つ曲を使用したい場合は、グリッドを複製し、プログラムのgrids変数に追加してください。その際、channelsも忘れずに追加するようにしてください!
プログラム全体の解説
MidiGridプログラムで何が行われているのかを順を追って説明します。
- Udon Graph
UdonSharp
Startイベント:

On Startで、grids配列内の各オブジェクトを順に処理し、その子要素にある「Image」コンポーネントを探してenabledの値をfalseに設定することで、初期状態で全てのImageを非表示にします。
また、ロード完了後に1秒間待機してからVRCMidiPlayerのPlay()を呼び出し、音楽とデータの再生を開始します。
Note Events:

Midi Note Onイベントを受信すると、channels配列の各エントリをループ処理し、受信したノートのチャンネルがエントリのいずれかと一致するかを確認します。一致するものが見つかった場合、その番号がgrids配列のindexとして使用され、対応するグリッドが特定されます。受信したノートに対してint.Remainder()を実行し、オクターブ内でのインデックス(Cなら0、C#なら1など)を算出します。このインデックスを使用してグリッドの適切な子要素を特定し、その「Image」のenabledをtrueに設定します。最後に、そのノートのチャンネルとノート番号がコンソールに記録されます。

スクリプトがMidi Note Offイベントを受信すると、上記と同様の処理を行います。「Image」コンポーネントを再び非表示にするため、enabledをfalseに設定します。
using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using VRC.SDK3.Midi;
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LogoButton : UdonSharpBehaviour
{
[SerializeField] private Transform[] grids;
[SerializeField] private VRCMidiPlayer player;
[SerializeField] private int[] channels;
private void Start()
{
// Disable Image components of all grid children
foreach (var grid in grids)
{
for (var i = 0; i < grid.childCount; i++)
{
var child = grid.GetChild(i);
var image = child.GetComponent<Image>();
image.enabled = false;
}
}
SendCustomEventDelayedSeconds(nameof(_PlayAudio), 1);
}
public void _PlayAudio()
{
player.Play();
}
public override void MidiNoteOn(int channel, int number, int velocity)
{
UpdateGridState(channel, number, true);
Debug.Log($"{channel} : {number}");
}
public override void MidiNoteOff(int channel, int number, int velocity)
{
UpdateGridState(channel, number, false);
}
private void UpdateGridState(int midiEventChannel, int midiEventNoteNumber, bool isEnabled)
{
// Find all grids that are mapped to the midi event's channel.
for (var gridIndex = 0; gridIndex < grids.Length; gridIndex++)
{
var gridChannel = channels[gridIndex];
if (midiEventChannel != gridChannel) continue;
// Enable/Disable image, quantized by chromatic 12 note scale.
var child = grids[gridIndex].GetChild(midiEventNoteNumber % 12);
var image = child.GetComponent<Image>();
image.enabled = isEnabled;
}
}
}
最終更新: