7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Unity + MIDI で テンポ(BPM)を制御してみる

Last updated at Posted at 2017-02-24

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を弄りたい人が見てくれたら幸いです。ほぼほぼ自分のメモになってますが....。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?