ビー玉レースの実況を自動で付ける
実況は、レースを見て話すのではなく、計算の記録を読んで話す。8 つのビー玉が転がるあいだに起きた出来事を拾い、言う文を選び、話す時刻を決め、声と字幕にした。文の型は前もって用意し、レースを見て書き足した文は無い。
レースと実況
Matter.js 0.20 で 8 つのビー玉を同じコースに転がし、計算の記録から実況を自動で付けた。玉は白黒の模様と番号で見分ける。乱数の種は 75 で、同じ種なら何度作っても同じレースになる。
コースは、ジグザグの坂 4 本・釘の林 7 段・回る風車 2 つ・1 列になる細い筒・最後の坂 2 本で、スタートからゴールまでの高さは約 4100 px ある。5 番がスタートから 14 秒間先頭を守り、そのあと 3.0 秒の間に先頭が 4 回入れ替わった。門が開いてからの秒数で、1 着は 7 番(21.06 秒)、2 着は 8 番(21.41 秒)、3 着は 3 番(21.82 秒)である。
計算のしかた(ベレ法・反発係数・乱数の種)は 物理エンジンで作る縦の短い動画と、その中の数学 に書いた。この記事は、実況の付け方だけを書く。
4 つの段階
計算のあと、実況は 4 つの段階で作る(図の「記録」から「書き出し」まで)。
- 記録を取る。1/120 秒ごとの計算のうち、1 コマ(1/30 秒)ごとに 8 つの玉の位置・回転・速さを残し、Matter.js が知らせる衝突(collisionStart)をすべて残す[4]。
- 出来事を拾う。記録から、先頭の入れ替わり・上位の衝突・区間への到着・大きな順位の変化・ゴールを、決まった条件で取り出す。
- 文の候補を作る。出来事ごとに、言い方の決まった文を選ぶ。どの言い回しを使うかも、種から作った乱数で決める。
- 割り付けて書き出す。声の長さを測ってから、重ならないように話す時刻を決め、字幕・声・効果音・BGM を混ぜて mp4 にする。
出来事を拾う
コースはすべて下へ進むので、「どれだけ進んだか」は玉の高さ(画面の y 座標)で比べられる。ゴールした玉は、ゴールした順に上に並べる。これで 1 コマごとの順位が決まる。
順位をそのまま使うと、並んで転がる 2 つの玉の先頭が 1 コマごとに入れ替わり、実況が追いつかない。そこで、新しい先頭が 0.4 秒続いたときだけ「先頭の入れ替わり」と数えた。画面の上の順位の表示も、y 座標を 0.3 の割合でならしてから並べた。
| 出来事(優先度) | 拾う条件 |
|---|---|
| スタート・ゴール・結果(4〜5) | 門が開いた時刻・y が 4470 px を越えた時刻 |
| 先頭の入れ替わり(4) | 新しい先頭が 0.4 秒続いた |
| 区間に入った(3) | 先頭がその区間の高さを越えた |
| 風車にはじかれた(3) | 上位 4 位の玉が風車に相対速度 250 px/s 以上で当たり、2 秒で 2 つ以上順位を落とした |
| 一気に追い上げた(3) | 3 秒で 3 つ以上順位を上げた |
| 大きく後退した(2) | 3 秒で 3 つ以上順位を落とした |
| 強い衝突(2) | 上位 4 位どうしが 420 px/s 以上の相対速度で当たった(2.5 秒あける) |
| 順位の読み上げ(1) | 2.5 秒ごと(間をつなぐ) |
種 75 のレースでは、この条件で 32 件の候補ができた。内訳は、先頭の入れ替わり 5(最初の先頭を含む)・順位の読み上げ 8・区間 4・追い上げと後退 6・衝突 3・ゴール 3・始まりと結果 3 である。風車ではじかれた玉は条件に合わず、0 件だった。
時刻に合わせる
候補の文をすべて読むと、声が重なる。どれを、いつ話すかを決める手順はこうである。
-
出来事 は、起きた時刻 ・優先度 ・遅れてよい秒数 を持つ。文の長さ は、先に声を合成して測る。
-
話し始めの時刻 は、出来事より前にはできず、遅れすぎてもいけない。遅れてよい秒数は出来事ごとに 0.3〜2 秒に決めた(先頭の入れ替わりは 0.9 秒、区間は 1.5 秒)。
-
すでに決めた文 とは、間 秒をあけて重ならないようにする。
-
優先度の高い順に(同じなら早い順に)、2 と 3 の条件を満たすいちばん早い時刻を選ぶ。大事な文が先に場所を取り、読み上げのような文は空いた所に入る。
-
入らなければ、短い言い換え(「先頭が入れ替わった、4番」の代わりに「今度は4番」)で探し直す。先頭の入れ替わりのように、展開が速くなりやすい種類の出来事では、言い換えのうち話し終わりがいちばん早いものを選ぶ。それでも入らなければ、その文は使わない。
-
最後に、話し始めの時点でまだ正しいかを確かめる。先頭の入れ替わりを言う前に、別の玉が先頭になっていたら使わない。
この手順で、32 件の候補から 16 文が残った。先頭の入れ替わりの 5 文はすべて残り、衝突の 3 文と追い上げ・後退の 6 文は 1 つも残らなかった。外れた 9 件のうち 6 件は、先頭が 3.0 秒に 4 回変わる場面(動画の 20 秒前後)に重なり、先頭の文が時間を使い切った。残りの 3 件は、区間の文やゴールの文と重なった。
| 起きた | 話した | 遅れ | 文 |
|---|---|---|---|
| 17.45 | 17.45 | 0.00 | 今度は4番! |
| 18.15 | 18.47 | 0.32 | また5番! |
| 18.75 | 19.23 | 0.48 | 3番がここで前に出た! |
| 20.48 | 20.73 | 0.25 | 7番がここで前に出た! |
| 20.90 | 22.20 | 1.30 | さあ最後の坂だ! |
| 24.06 | 24.06 | 0.00 | 7番、1着でゴール! |
| 24.41 | 25.83 | 1.42 | 2着は8番、わずかな差! |
(時刻は動画の秒。門が開くのは 3.0 秒なので、画面の時計より 3 秒大きい。先頭の入れ替わりは、前に出たと決まった時刻の 0.15 秒後を「起きた」時刻にしている)
いちばん遅れたのは 2 着の文で、1.42 秒遅れた。1 着の文を話している間に 2 着がゴールしたためで、3 着の文は入る場所が無く使わなかった。3 着は、最後の結果の読み上げで伝えている。
声と字幕と音
声は VOICEVOX の玄野武宏である。エンジン(0.26)を作業用のサーバーで動かし、文ごとに合成した。話す速さは 1.18 倍・抑揚は 1.35 倍にし、「スタート!」だけ速さ 1.05 倍・抑揚 1.5 倍にした。1 着の文だけ「喜び」の声を使った。
玄野武宏の音声は「VOICEVOX:玄野武宏」と書けば商用でも使える[1][2]。規約が禁じる使い方(政治や宗教の宣伝、合成した音声で別の音声のモデルを学習させることなど)には当たらない。動画の最後の画面と、動画の配信の説明、この記事の動画の下にこの表記を入れた。青山龍星の声は、企業や個人事業主が使う場合に事前の申請が要るため使わなかった。実在の人の声をまねる使い方はしていない。
字幕は、白い縁の黒い帯に白い字で、文字は画面の下から 420 px より上に置いた。縦の動画では、下にアカウント名や説明文が重なるためである。
声が BGM に埋もれないよう、話している間は BGM を 0.3 倍、効果音を 0.5 倍に下げた。声の区間で音の強さ(RMS)を測ると、声は -14.4 dBFS、下地(BGM と効果音)は -24.3 dBFS で、声が約 10 dB 上にある。最後に動画全体の音の大きさを -14 LUFS にそろえた。LUFS は EBU R 128 などが定める音の大きさの測り方である(放送の基準は -23 LUFS)[3]。
効果音と BGM はすべて合成した。ガラス玉の当たる音は 2 つの高い音(2.6〜3.5 kHz と 4.1〜5.4 kHz)とごく短い雑音で作り、当たった速さで大きさを、画面の左右の位置で鳴る方向を変えた。釘とこぶの音は、当たった横の位置で 5 音の音階のどれかを鳴らす。素材サイトの音を使う場合の比べ方は、サービス・システム大全の効果音の素材サイト 12 選にまとめてある。
できないこと
この実況は、決まった条件と決まった文の組み合わせである。そのため、できないことがはっきりしている。
- 条件に書いていない出来事は話さない。風車で大きく跳ねても、順位が 2 つ以上変わらなければ黙っている。
- 理由は話さない。7 番がなぜ最後に抜け出したか(どの衝突で前に出たか)は、記録には残っているが、文にしていない。
- 言い回しは用意した数しかない。種を変えて何本も作ると、同じ文が繰り返し聞こえる。
- 遅れてよい秒数は出来事ごとに 0.3〜2 秒に決めてある。このレースでいちばん遅れたのは 1.42 秒だった。短い言い換えを増やすほど、遅れは小さくなる。
先頭を言う文は、話し始めの時点で先頭を確かめてから使う。そのため、話し始めた瞬間に画面の先頭が別の玉ということは無い。ただし、話している 1 秒ほどの間に入れ替わることはある。
参考文献
- VOICEVOX 利用規約. voicevox.hiroshiba.jp/term
- VirVox Project, VOICEVOX の利用規約(玄野武宏). virvoxproject.com
- EBU, R 128 Loudness normalisation and permitted maximum level of audio signals, Version 5.0 (2023). tech.ebu.ch
- Matter.js 0.20.0 のソース(src/core/Events.js・src/collision/Pair.js). GitHub liabru/matter-js