ゲームとか音楽プレイヤーを作っていて requestAnimationFrame
でループ処理をしていたのですが、FPS や BPM で処理を実行できるようにしたかったのでやってみてたメモ
requestAnimationFrame 基本のループ
let requestID; function loop(now = 0) { // 処理 requestID = requestAnimationFrame(loop); } // ループ処理の開始 requestID = requestAnimationFrame(loop); // ループ処理を停止 function stopAnimation() { requestAnimationFrame(requestID) }
FPS
FPS (Frame Per Second) は 1秒間のコマ数で表される
5FPS なら 1秒間に5コマ、50FPS なら 1秒間に50コマを表示する速度
これを JavaScript で実現するには、実行時間を 1秒をFTPで割った数で割った値の実数部分をコマ数とすれば良い。
JavaScript の 1秒は 1000ms で考えるので、次のような計算になる。
const frame = Math.floot( time / ( 1000 / fps ) );
たいてい毎秒 60 回ですが、一般的に多くのブラウザーでは W3C の勧告に従って、ディスプレイのリフレッシュレートに合わせて行われます。ただし、コールバックの確率は、バックグラウンドのタブや隠れた
<iframe>
では、パフォーマンス向上やバッテリー消費を減らすために低くなるでしょう。
cf. Window.requestAnimationFrame() - Web API | MDN
requestAnimationFrame はおおよそ 60f/s で動作するので同じフレームが何度も呼び出されないようにしてあげればOK
const fps = 30; let count = 0; let requestID; let time = 0; let preFrame; function loop(now = 0) { let frame = Math.floor((now - time) / (1000 / fps)); // fps のフレーム数に達したらもう一度 0 フレームから始める場合 // frame は 0 から始まるので ftp - 1 まで if (frame > fps - 1) { time = now; count += 1; frame = 0; } // 開始直後 frame がマイナスになることがあるので 0 以上を条件にする if (frame >= 0 && preFrame !== frame) { // フレームごとの処理 console.log(count, frame + 1); } preFrame = frame; requestID = requestAnimationFrame(loop); } !function() { time = performance.now(); requestID = requestAnimationFrame(loop); }();
BPM
BPM (Beats Per Minute) は 1分間に4分音符の拍数で表される
120 BPM なら 60秒間に 4分音符なら 120回、 8分音符なら4分音符2回分なので 120 * 2回、 2分音符なら4分音符の倍の長さなので 120 * 0.5回。
60秒 をこれらの数で割った値が、各音符の時間間隔になる。計算式にすると次のような感じ
const beat = 16; // 演奏する音符 const beatRate = 60 * 1000 / ( bpm * (beat / 4) ); const frame = Math.floor(time / beatRate);
フレーム計算ができれば後は FTP と同じ
const MINUTES = 60 * 1000; const BASE_BEAT = 4; // BPMが4分音符換算なので定数的に扱う const bpm = 120; const beat = 16; // 実際にカウントするベースになる音符 const beatRate = MINUTES / (bpm * beat / MINUTES); let section = 1; let requestID; let time = 0; let preBeatCount; function loop(now = 0) { let beatCount = Math.floor((now - time) / beatRate); // beatCount (音符の数)が bpm (1章節) に入る数に達したら beatCount を 0 に戻す if (beatCount > beat - 1) { time = now; section += 1; beatCount = 0; } if (beatCount >= 0 && preBeatCount !== beatCount) { // beat 音符毎に実行する処理 console.log(section, beatCount + 1); } preBeatCount = beatCount; requestID = requestAnimationFrame(loop); } !function() { time = performance.now(); requestID = requestAnimationFrame(loop); }();
SAMPLE
See the Pen requestAnimationFrame FTP / BPM by KIKIKI (@kikiki_kiki) on CodePen.
所感
多分超厳密なものではないと思うけど、 FPS / BPM を実現することが出来ました。
FPS / BPM は一定の間隔で処理をするものなので、テトリスとかを作る場合はレベルに応じてフレームが実行される間隔の時間が短くなる計算にすれば良さそう。
今回コレを作るにあたって BPM とか調べてて、テンポ (tempo) がイタリア語だってことを知りました。
そういえば、ドレミってイタリア語からだったので、日本の音楽は伝統的にイタリアの影響が強いのでしょうか。興味深いです。
[参考]
- Window.requestAnimationFrame() - Web API | MDN
- window.cancelAnimationFrame - Web API | MDN
- requestAnimationFrame でフレームと再描画更新を制御する | Yomotsu net
- JavaScript アニメーション
- JavaScriptのフレームレートについて考える | Tips of Rubbish
- フレームレート(FPS)って何?どのくらいの値にすればいいの?
- テンポ - Wikipedia
- 【コラム】音楽の仕組み #24 テンポについて | NOAHBOOK

- 作者:Ed Catmull,Amy Wallace
- 発売日: 2014/11/04
- メディア: Kindle版
![[ゲーム&モダンJavaScript文法で2倍楽しい]グラフィックスプログラミング入門 ——リアルタイムに動く画面を描く。プログラマー直伝の基本 (WEB+DB PRESS plus) [ゲーム&モダンJavaScript文法で2倍楽しい]グラフィックスプログラミング入門 ——リアルタイムに動く画面を描く。プログラマー直伝の基本 (WEB+DB PRESS plus)](https://m.media-amazon.com/images/I/51kaNsZ+KjL._SL160_.jpg)
[ゲーム&モダンJavaScript文法で2倍楽しい]グラフィックスプログラミング入門 ——リアルタイムに動く画面を描く。プログラマー直伝の基本 (WEB+DB PRESS plus)
- 作者:杉本 雅広
- 発売日: 2020/01/18
- メディア: 単行本(ソフトカバー)