tanaka's Programming Memo

プログラミングについてのメモ。

曲のビートのタイミングを計算する方法

f:id:am1tanaka:20201216204931g:plain

曲に合わせた動きは、気持ちよい演出が作りやすいです。

Unityでこれをやるものとして、geekdrumsさんのMusicEngineが有名です。

github.com

1週間ゲームジャムで公開したYOKETORU改2019を開発する時に利用しました。開発を進めるうちに、MusicEngineが提供してくれるデータ以外のものも欲しくなったため、結構自前であれこれ計算してました。

Unityは曲の再生位置を秒数やデータの進み具合で知ることができるので、BPMさえ分かっていれば自前でビートのタイミングは算出できます。ならば全部自前で計算しようということで、直近で作成したJUMP OR DIEで全て自前で実装しました。

unityroom.com

その時の知見を元に、ビートの算出方法を紹介します。

目次

曲のテンポを把握する

BPM

曲のテンポを表す値としてBPMがあります。Beat Per Minutesの略で、1分あたりのビート=拍子数です(ちなみに小節はBar)。1分間あたりの拍数が分かれば、60をBPMで割ってやれば、1拍の秒数が分かります。

JUMP OR DIEのBGMで利用した曲は、 D-elf.com さんのものを使いました。D-elf.comさんのサイトは、曲のBPMが書かれているので有難いです。

D-elf.com

BPMを推定する

BPMが分からない曲は、以下のおとわびさんのBPMタップテンポはかるくんのページに行けば自分で推定できます。

otowabi.com

曲を流して、テンポに合わせてクリックを続けると、クリックのタイミングの平均から推定のBPMを表示してくれます。数字が安定したら、切りのよい数字をBPMとして採用します。

曲の経過を表すtimeSamples

Unityでは曲の開始からの経過を、AudioSourceのtimeならfloatの秒数で、timeSamplesならintで録音データの位置で読み取ることができます。マニュアルを確認すると、より厳密なタイミングを処理したい場合はtimeSamplesを使いましょうとあります。

Use this to read current playback time or to seek to a new playback time in samples, if you want more precise timing than what time variable allows.

録音データは1秒の間に設定回数の音の変化を記録したものです。この回数は、AudioClipのfrequencyプロパティで確認できます。44,100Hzという値がよく利用されますが、これはCDで使われている値で「1秒間に音データを44,100回記録したデータである」ということです。timeSamplesは、今、再生している音データが何回目のものかを表すものです。44,100Hzなら、timeSamples44,100になったら、曲が開始してから1秒経過したことになります。

120BPMでの計算例

以下の図は、BPMを120とした場合のあれこれの値の例です。

f:id:am1tanaka:20201215150649p:plain
120BPMの時の値

  • 120BPMとは、1分(=60秒)に120ビートあるということ
  • 1ビートあたりの秒数は 60 [秒] / BPM で計算できます。120BPMなら、60 / 120 で、1ビート0.5秒です
  • 音データのサンプル数が 44,100 [Hz] だった場合、0.5秒では 0.5 [秒] * 44,100 [Hz] = 22,050 です。これが1ビートで進むtimeSamplesの値です

つまりtimeSamples22,050の倍数を跨ぐ時に何かをやれば、リズムに合わせた動きを作ることができます。

注:割り切れる場合は「22,050ごと」という考え方でよいですが、割り切れないような場合は誤差を防ぐため、検出したいビート数から該当するサンプル値を算出した方がよいでしょう。

もう一つの値として、1回のFixedUpdateごとにサンプル値がどれぐらい進むかも求めておくと判定や動き出すタイミングを計算する役に立ちます。AudioClipの周波数が44,100 [Hz]で、Time.fixedDeltaTimeの値がデフォルト値の0.02なら、 44,100 [Hz] * 0.02 [秒] なので、1回の更新でtimeSamples882進むことになります。

22,050から前後882以内なら、ぴったり。さらに882離れるとちょっとズレている。もう882ずれているとまあまあズレている。というような判定ができます。

実装例

以下のGitHubリポジトリーで、実装例のUnityプロジェクトを公開しました。プロジェクトの設定方法はリポジトリーの方のドキュメントを参照ください。

github.com

曲は、先に紹介した d-elf.com さんから「Love This Beat / Free BGM ver.1」を利用しました。曲データはプロジェクトには含まれないので、ご自身で以下のページからダウンロードして、プロジェクトに設定してください。BPMは130で、サンプルプロジェクトに設定済みです。

www.d-elf.com

勿論、他の曲でも構いません。設定した曲のBPMを、BeatDetectorBeat欄に設定してください。

その他のTips

音源の調整

曲データには、冒頭に空白部分が含まれているものがあります。その場合、timeSamplesをそのまま使うとタイミングがずれてしまいます。曲がどこから始まるかを調べたり、空白部分をトリミングしたりする時にAudacityというツールが便利です。

https://www.audacityteam.org/

日本語でダウンロードする場合はこのあたりからどうぞ。

forest.watch.impress.co.jp

効果音の不要な空白をトリミングしたりするのにも重宝します。

有料アセット

今回はBPMを自前で計って実装しましたが、お金を出せば音源データから自動的にリズムを取り出してくれたり色々してくれるアセットもあります。

Rhythmator

assetstore.unity.com

つい先日買ったので持ってはいるんですがまだ使ってません。いずれ紹介記事など書くかも知れません。

まとめ

BPMを使ってビートを計算する時の考え方とサンプルを紹介しました。1分間に何ビートかと、1秒間のサンプル数が分かれば、単純な計算でタイミングは求めることができます。これを応用すれば、オブジェクトをあるビートのタイミングで判定ライン上を通過させるとか、曲に合わせてジャンプさせるとか、弾を撃ったりといった演出ができます。

お金で解決することも可能ですが、計算方法を知っていれば欲しいタイミングを全て算出できます。いざという時に何かの役に立つのではないかと思います。

参考・関連URL