曲から踊りのモーションを自動で作る|拍の推定と、自然に見せるための工夫

このサイトでは、曲をドロップするだけで、その曲に合わせて踊るVMDを作れます。中身は「曲から拍を見つける」と「拍に合わせて姿勢を動かす」の2段で、機械学習は使っていません。作りながら測ったこと、外れたこと、直したことをまとめます。

最終更新: 2026-10-07 · 拍の推定は作者がBPMを公開している実物の12曲(incompetech、CC BY)と合成音のテストで確認。髪の揺れは人物Aで1/30秒刻みに物理を進めて測り、実物のVMD(ad.vmd・kimino.vmd・gal.vmd・kabe.vmd)と比べた

試すだけなら曲に合わせて踊るモーションを作るへ曲をドロップしてください。 できたVMDはダウンロードでき、そのまま手元のMMDを再生で踊らせることもできます。どちらもアップロード・ログイン不要です。

全体の流れ

  • 曲を解読する。 ブラウザの decodeAudioData() で波形にし、左右を1本にまとめます。
  • 拍を見つける。 音の立ち上がりの強さを並べ、その繰り返しの間隔からBPMを、低音の位置から1拍目を決めます。
  • 拍に合わせて姿勢を作る。 曲の盛り上がりに応じて動きの型を選び、時刻の関数として姿勢を出します。
  • VMDに書き出す。 3フレームおきにキーを置きます。3分24秒の曲で、キー2,041個・3.2MB・生成0.4秒でした。

拍(BPM)の見つけ方

まず、短い区間ごとに周波数ごとの音の強さを出し、前の区間より強くなった分を足し合わせます(対数スペクトルの増え方)。太鼓やベースが鳴った瞬間に大きくなる値の列です。この列が何フレームおきに似た形を繰り返すかを自己相関で調べ、その間隔をBPMにします。

作りながら、合成音(キック + 裏拍のハイハット + 雑音)のテストで3つの穴を踏みました。

起きたこと原因直し方
BPMが0.6ずれる自己相関の間隔は1フレーム(約11.6ms)刻みでしか取れない曲全体で拍の位置が揃う間隔を、1フレームより細かく探して詰める
裏拍に合わせて踊る全部の周波数で1拍目を決めると、帯域の広いハイハットの合計が大きくなる200Hz以下(キック・ベース)を重く見る
150 BPMを75と取る間隔が34.45フレームのように半端だと、整数のずれで相関が下がる自己相関の前に2フレームぶんぼかす

直したあと、合成音の4つのテンポ(96・128・150・174)で、BPMの誤差は0.5未満、拍の位置のずれは30ms未満になりました。

実物の曲でどこまで合ったか

作者がBPMを公開している実物の12曲(incompetech の CC BY の曲)で確かめました。3分の曲の解析にかかる時間は1〜1.6秒(Chromium)です。

結果曲数内訳
作者のBPMと一致8
倍・半分・2/3で取った3155→103(2/3)、200→100、204→95
外れ170→140

倍・半分に取ると、踊りは速すぎるか遅すぎるように見えますが、拍の位置自体は合っているので、BPMを×2・½するだけで直ります。道具の画面には、BPMと1拍目の入力欄、×2・½・×1.5・÷1.5のボタン、手で叩いて合わせるボタン、拍の音を曲に重ねて20秒聴くボタンを置いています。

拍の位置も確かめました。正解の拍の時刻は無いので、推定した拍の上の立ち上がりの強さを、ずらした位置と比べています。BPMが合った8曲で、半拍ずらした位置の1.09〜5.05倍、1/4・3/4ずらした位置の1.6〜6.4倍でした。裏拍や16分の位置に合ってしまった曲はありませんでした。

推定には「確からしさ」(0〜1)も出しています。BPMを取り違えた4曲は0.21〜0.44で、合っていた8曲は1曲(0.23)を除いて0.60以上でした。そこで0.5未満のときだけ「×2・½で直せます」と注意書きを出しています。低いのに合っている曲もあるので、外れの目安でしかありません。

踊りの組み立て方

最初の版は、「拍ちょうど」と「半拍後」の2つの姿勢を直線でつなぐだけでした。これだと拍のたびにカクッと折り返し、途中で腕が真横に開いた形(Tポーズに近い形)を何度も通ります。作り直した版では、次のようにしています。

  • 姿勢を時刻の連続した関数として作り、3フレームおきにキーを置く。 上下の動きは拍で沈む cos の波なので、動き出しと止まり際がゆっくりになります。キーのあいだはVMDの補間に任せます。
  • 体重移動を入れる。 センターを左右へ動かし、腰を体重の側へ傾け、上半身は逆へ少し傾けます。首・腕・手首は体より0.12拍遅らせます。
  • 腕は真横を通らない。 下げた腕を前後に振るか、前を回して上げます。テストで「腕が水平に近いときは前を向いている」ことを全部の型について見ています。
  • 型は6つ。 落ち着き・ノリ・盛り上がりを2つずつ用意し、8拍ごとに曲の立ち上がりの強さで選びます。
  • ステップの型だけ足を浮かせる。 体重が乗っていない側の足IKを少し上げます。ほかの型では足は床から動きません。
  • まばたきを2〜5秒おきに入れる。 頭もゆっくり揺らします。

型の切り替えでも穴を踏みました。8拍の区間の境目を1拍で混ぜると、腕を下ろした型から両手を上げる型へ移るときに1キーで28°回りました。いまは前後1拍ずつ、計2拍かけて混ぜています。また、区間ごとに左右の向きを入れ替えると、境目で体重が逆の側へ一気に移り、センターが1キーで0.4動きました。左右の向きは曲を通して固定しています。片手ずつ突き上げる型も、2拍で1往復だと腕が1拍で140°回って速すぎたので、4拍で1往復にしました。

自然に見えるかを数字で確かめる

「自然に見えるか」は目で判断しにくいので、髪の揺れ方を実在のダンスモーションと比べました。動きが不自然に速ければ髪は跳ね、少なすぎれば髪はほとんど揺れません。動画の書き出しと同じ1/30秒の固定刻みで物理を進め、ツインテールの先が頭に対して動く速さを、人物Aで15秒ぶん測っています。

モーション中央値上位5%
作り直す前2.25.4〜5.9
作り直した後5.6〜6.39.5〜11
ad.vmd(このモデル向けの実物のダンス)6.3〜6.814〜20
kimino.vmd(激しいダンス)16〜2146〜50
gal.vmd・kabe.vmd(ゆっくりした動き)0.9〜4.85.5〜9.7

作り直した後は、実物のダンスと同じくらいの揺れになりました。前の版の揺れが小さかったのは、動きそのものが少なかったためです。

物理の見た目は、再生しながら撮った画面で判断してはいけません。 一度、描画が毎秒1.3フレームしか出ていない環境で見て「髪が真横に跳ねる」と判断し、誤りでした。物理は描画1回ぶんの時間をまとめて進めるので、遅い環境では実際とは別の揺れ方になります。固定の刻みで進めて数字で比べてください。

ほかに、足首の高さは1.39〜1.99(ステップで浮かせた分を含む)、前後左右は±1.09に収まり、手首と体の軸の距離は最小2.63で、手が体にめり込まないことも確かめました。

曲とモーションは周回ごとにずれる

できた踊りを曲と一緒にループ再生して、音の再生位置とモーションの再生位置の差を5秒おきに測ったところ、2周目から2.3秒ずれ続けていました。

  • <audio loop> は曲の長さ(204.07秒)で頭へ戻る。
  • three.js の MMDAnimationHelper は、モーションを「最後のキー + 余韻(afterglow)2秒」(206.2秒)で頭へ戻す。
  • 1周目も、音とモーションの読み込みが別々に終わるので0.33秒遅れていた。140 BPMの拍の間隔(0.43秒)の3/4で、見て分かるずれ。

そこで、音を正にしてモーションを合わせるようにしました。250msおきに差を見て、0.12秒を超えるずれが2回続いたらモーションをシークします。1回で直すと、描画が一瞬止まったときに「遅れて見える → シーク → 追いついて進みすぎ → もう一度シーク」の往復が起きました。直した後は差が±0.05秒以内で、シークは始まりの1回と周回ごとの1〜2回だけです。自動の踊りに限らず、BGMとモーションを一緒に再生するときはどれも同じなので、このサイトの再生画面全部に入れています。

できないこと

  • 振付らしさは無い。 型を拍に合わせて動かすだけで、曲の歌詞や構成を表す動きにはなりません。
  • 途中でテンポが変わる曲は追えない。 曲全体を1つのテンポとして扱います。
  • 口・表情は動かさない。 入れているのはまばたきだけです。
  • モデルに標準ボーンが必要。 センター・上半身・腕・ひじ・足IKなどの名前で動かすので、名前の違うモデルでは動かない部分が出ます。

再生画面(シーン・手元のMMDを再生)のモーション欄でも「自動: BGMに合わせて踊る」を選べます。選んでいるBGMからその場で踊りを作り、シーンとして保存すれば、開き直したときも同じ選択に戻ります。