UnityでMIDIファイルを読み込む時にBPMを制御する
Unityでmidiデータを再生しBPMも弄りたいと思ったらわりと簡単に出来たので残しておこうと思います。
前提
UnityでMIDIデータを扱うためには以下の記事を参考にさせて頂きました。
n-yoda さんのブログ
http://ny.hateblo.jp/entry/2016/01/21/230640
使用ライブラリ
C# Synth Project
https://csharpsynthproject.codeplex.com/
BPMが設定されている箇所
MIDIデータを読み込む時にBPMを設定しているスクリプトは以下のファイルに含まれています。
CSharpSynth/
└ Source/
└ AudioSynthesis/
├ Bank/
├ Midi/
¦
├ Sequencer
├ MidiFileSequencer.cs <- このファイル
└ MidiInputSequencer.cs
¦
MidiFileSequencer.cs の LoadMidiFile() 内でMidiファイルからBPMを取得するように記述されている。
private void LoadMidiFile (MidiFile midiFile)
{
//最初にBPMを120に設定
BPM = 120.0;
//---
if (midiFile.Tracks.Length > 1 || midiFile.Tracks [0].EndTime == 0)
midiFile.CombineTracks ();
mdata = new MidiMessage[midiFile.Tracks [0].MidiEvents.Length];
eventIndex = 0;
sampleTime = 0;
double absDelta = 0.0;
for (int x = 0; x < mdata.Length; x++) {
MidiEvent mEvent = midiFile.Tracks [0].MidiEvents [x];
mdata [x] = new MidiMessage ((byte)mEvent.Channel, (byte)mEvent.Command, (byte)mEvent.Data1, (byte)mEvent.Data2);
absDelta += synth.SampleRate * mEvent.DeltaTime * (60.0 / (BPM * midiFile.Division));
mdata [x].delta = (int)absDelta;
//MIDI内にBPMの記述がある場合読み込む
if (mEvent.Command == 0xFF && mEvent.Data1 == 0x51)
BPM = Math.Round(MidiHelper.MicroSecondsPerMinute / (double)((MetaNumberEvent)mEvent).Value, 2);
//---
}
totalTime = mdata [mdata.Length - 1].delta;
}
MIDIをファイルを読み込む時にBPMを指定してロード出来るように
LoadMidiFile()はLoadMidi()内で呼ばれるので新しく引数にBPMを持つLoadMidi()を用意してみる。
public bool LoadMidi (MidiFile midiFile)
{
if (playing == true)
return false;
LoadMidiFile (midiFile);
return true;
}
//新しく作ったメソッド
public bool LoadMidi (MidiFile midiFile, int bpm)
{
if (playing == true)
return false;
LoadMidiFile (midiFile);
BPM = (double)bpm;
return true;
}
とても単純ですが....これだけでBPMが弄れます。
せっかくなのでBPMの計算についてUnity内でどのように記述したかもメモしておきます。
BPMを計算するために
Unityでは様々なTime情報を扱うインターフェイスがありますが今回はDeltaTimeを使用してタップテンポを実装してみました。
余談ですがAudio周りで音をループするためにTimeを切りたい時はdsptimeを使用するのが良いです。(自分が気付くまでに時間が掛かっただけですorz)
実際にテンポを計るスクリプトが以下になります。
float timeCount = 0;
float timeDuration = 0;
bool isActiveTapTempo = false;
float[] bpmarray;
float totalbpm = 0;
int bpmcount = 0;
int bpm_accuracy = 15;
bool firsttap = true;
public int bpm = 120;
public void FixedUpdate ()
{
//タップしてからある程度時間が経つとbpmの計測を終了
if (timeCount - timeDuration > 1.8) {
clearTempo ();
}
if (isActiveTapTempo) {
timeCount += 1.0f * Time.deltaTime;
}
}
public void tapTempo ()
{
//最初に呼ばれる時(最初にタップされる時)
if (firsttap) {
isActiveTapTempo = true;
//関数が呼ばれた時の時間を記録
timeDuration = timeCount;
//bpmの平均値の幅を設定
//bpm_accuracyを大きくするほど途中でテンポを変えにくくなる
bpmarray = new float[bpm_accuracy];
firsttap = false;
}
//2回目以降に呼ばれる時
else {
//前のタップ時刻 - 今のタップ時刻 = タップ間の時間
timeDuration = timeCount - timeDuration;
//秒からBPMに直して代入
bpmarray [bpmcount] = 1 / timeDuration * 60;
//今までのbpmを足す
for (int i = 0; i < bpmarray.Length; i++) {
totalbpm += bpmarray [i];
}
//bpmの平均値を出力
if (bpmarray [bpmarray.Length - 1] != 0) {
bpm = Mathf.RoundToInt (totalbpm / bpmarray.Length);
} else {
bpm = Mathf.RoundToInt (totalbpm / (bpmcount + 1));
}
//タップ時刻の更新
timeDuration = timeCount;
//bpmarrayカウンタの更新
if (bpmcount < bpmarray.Length)
bpmcount++;
if (bpmcount == bpmarray.Length)
bpmcount = 0;
//足されたbpmのリセット
totalbpm = 0;
}
}
public void clearTempo ()
{
bpmarray = new float[bpm_accuracy];
isActiveTapTempo = false;
firsttap = true;
bpmcount = 0;
totalbpm = 0;
timeDuration = 0;
timeCount = 0;
}
//return方法に関してはだいぶ手抜き
public int getTempo(){
return bpm;
}
おわりに
駄文で申し訳ないですが、Unity+MIDIでBPMを弄りたい人が見てくれたら幸いです。ほぼほぼ自分のメモになってますが....。