West Hill 開発メモ

カテゴリ: Tips

Unity上で楽曲をテンポ解析してBPMを推定する機能を作ってみました。

使い方
public AudioClip targetClip;

private void Start()
{
    int bpm = BpmAnalyzer.AnalyzeBpm(targetClip);
    Debug.Log("BPMは多分 " + bpm + " くらい");
}
AnalyzeBpmの中ではAudioClip.GetData()で取ってきたオーディオのサンプルデータを解析しています。

BpmAnalyzer.cs内で
サンプルデータをある程度で区切ってまとめるCreateVolumeArrayメソッドと、
実際にBPMを推定しているSearchBpmメソッドは、
処理時間が結構かかるので別スレッドから呼ぶように変更したほうが良いと思います。

※コルーチンみたいに使えるThread Ninja(Unity Asset Store)はおすすめ。
UnityEngine名前空間でもMathfなら別スレッドからでも呼べます。

また、例のシーンではメトロノームを使っていますが、実際の楽曲はテンポが動的に変わったりするので、万能ではないです。
楽曲を数秒おきに区切ってその範囲でBPMを推定する等が良いかもしれません。


参考
テンポ解析のアルゴリズム:
C/C++言語で音声ファイルのテンポ解析を行うサンプルプログラム 

AudioClip.GetDataで取れる値について:
このエントリーをはてなブックマークに追加 Clip to Evernote

Unity5.3で追加されたJsonUtilityを少し使ってみました。
http://docs.unity3d.com/ScriptReference/JsonUtility.html

JsonUtility.ToJsonJsonUtility.FromJsonで変換する。

・内部でUnityのシリアライザを使っているので、自前のクラスを含めたい場合、
MonoBehaviourとScriptableObjectを継承していればそのまま変換される。
そうでないクラスや構造体には[Serializable]属性を付けないと無視される。

・DictinaryやHashtableはシリアライズされないため、これも無視される。
回避策として、Json化したいクラスでISerializationCallbackReceiverを継承し、
OnBeforeSerializeOnAfterDeserializeでシリアライズ前、デシリアライズ後のタイミングをフックできるので、List等の別の型に置き換える。

以下、テスト用のコンポーネント。


このエントリーをはてなブックマークに追加 Clip to Evernote

UnityでオーディオをAudioClipから事前に解析する場合についてのメモ。
ss_audio_01
オーディオのサンプルデータはAudioClip.GetData
引数に入れた配列にサイズ分だけ取ってこれる。
※オーディオファイルのインポート設定がDecompress on Loadになっていないと取得できず、値が0の配列になる。

全データが必要な場合はサンプル数*チャンネル数分の配列を用意する。
//
// クリップの全サンプルデータ取得
float[] allSamples = new float[clip.samples * clip.channels];
clip.GetData(allSamples, 0);
第2引数はとってくるデータ開始位置までのオフセット。
秒数でオフセットを指定したい場合は秒数*サンプルレート*チャンネル数になる。
解析する際に数秒分のデータをスキップしたい時にもこの式が使える。
//
// オフセット2秒
float offset = 2f * clip.frequency * clip.channels;
AudioClip.GetDataで取得したデータは2chの場合、
LRLRLR…の順で-1~0と0~1の範囲に収まっていて、無音だと0になる。

このデータをゴニョゴニョした後、再生中にデータとの同期を取りたい場合、
再生中のオーディオソースからAudioSource.timeSamplesで現在の再生サンプル時間を取得できるので、これを使う。

このtimeSamplesでクリップからAudioClip.GetDataで取得した配列を参照する場合は、
timeSamples*チャンネル数が当該インデックスになる。
//
// 今ここ
float sample = samples[audioSource.timeSamples * clip.channels];

このエントリーをはてなブックマークに追加 Clip to Evernote

Unity4.5.3からResourcesディレクトリ配下のアセットの読込を非同期で行うResources.LoadAsync()メソッドが追加されたので試してみました。


戻り値であるResourceRequestで進捗の取得や優先度の設定が行えます。

このエントリーをはてなブックマークに追加 Clip to Evernote

Unity4.5からはコルーチンの停止に使うStopCoroutineメソッドで引数にIEnumratorを指定できるようになりました。
今まではStopAllCoroutinesで全部止めるか、StopCoroutineでメソッド名指定で止めるしか無かったので、便利になりました。
(※ただし後者はStartCoroutineをメソッド名指定で動かした場合に限る)
 
DoWorkCoroutineというコルーチンメソッドがあるとして、
こんな風にIEnumratorの参照を保持して利用します。
// IEnumerator保持
IEnumerator routineWork = DoWorkCoroutine ();

// コルーチン開始
StartCoroutine (routineWork);

// コルーチン停止
StopCoroutine (routineWork);
ただし、これで停止したコルーチンを同じIEnumratorを指定して再スタートさせた場合は
コルーチンの途中から再開してしまうので注意が必要です。
(※既に末尾まで終了している場合には何も起きないようにみえる)

なので、コルーチンを始めから再スタートさせたい場合には以下のようにする必要があります。
// IEnumerator保持
IEnumerator routineWork = DoWorkCoroutine ();

// コルーチン開始
StartCoroutine (routineWork);

// コルーチン停止
StopCoroutine (routineWork);

// コルーチン再スタート
// 同じIEnumeratorを指定すると途中から再開されてしまうので再取得する
routineWork = DoWorkCoroutine ();
StartCoroutine (routineWork);
逆に、IEnumratorの参照を取りなおさなければ
任意のコルーチンを途中で一時停止・途中から再開させる用途で使えます。

参照:MonoBehaviour.StopCoroutine

このエントリーをはてなブックマークに追加 Clip to Evernote

このページのトップヘ