MIDI 播放
此示例播放与 MIDI 生成的音频文件同步的 MIDI 文件。你可以使用 MIDI 曲目在世界中同步事件,将音乐和视觉效果结合在一起,获得沉浸式体验。
访问 MIDI 播放示例世界亲自体验!
技术解析
VRCMidiPlayer 类似于 AudioSource,但它使用 MIDI 资源,并将 Note On 和 Note Off 事件发送到 MidiGrid UdonBehaviour。
MidiGrid
此程序使用彩色方块可视化 MIDI Note On 和 Off 事件。
每当 VRCMidiPlayer 发送 Note On 事件时,程序将检查其通道是否与某个网格匹配。如果匹配,程序通过计算音符编号与 12 的余数来选择要启用的对应方块,以找到对应的图片。
当 VRCMidiPlayer 发送 Note Off 事件时,程序执行相同的计算来找到目标网格和图 片,但将其禁用。
检视面板字段
| 名称 | 描述 |
|---|---|
| grids | 对 RectTransform 的引用,其上有一个 GridLayoutGroup,包含对应一个完整八度音符的 12 个子 Image。 |
| channels | 用于将 MIDI 通道重新映射到场景中四个图片网格的整数数组。 |
| player | 对向 MidiGrid 发送事件的 VRCMidiPlayer 的引用。 |
完整程序说明
以下是 MidiGrid 程序中发生的事件的分解说明。
- Udon Graph
- UdonSharp
Start 事件:
启动时,遍历 grids 数组中的每个对象,找到其子对象上的'Image'组件,并将其 enabled 值设置为 false,从而隐藏所有图片。
Note 事件:
当收到 Midi Note On 事件时,遍历 channels 数组中的每个条目,检查传入音符的通道是否匹配。如果找到匹配,使用该数字作为 grids 数组的 index 来找到匹配的网格。传入音符通过 int.Remainder() 找到其在八度中的索引。使用此索引找到网格的相应子对象,并将'Image'的 enabled 设置为 true。
Midi Note Off 事件类似,但将 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()
{
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)
{
for (var gridIndex = 0; gridIndex < grids.Length; gridIndex++)
{
var gridChannel = channels[gridIndex];
if (midiEventChannel != gridChannel) continue;
var child = grids[gridIndex].GetChild(midiEventNoteNumber % 12);
var image = child.GetComponent<Image>();
image.enabled = isEnabled;
}
}
}