タップテンポ測定の精度を上げる仕組み: performance.now()・移動平均・揺らぎ補正
W3C High Resolution Time仕様に基づくブラウザのタップテンポ実装を技術解説。サブミリ秒精度・移動平均・自動リセット・ヒューマンエラー対策まで整理します。
タップ→BPM変換のパイプライン
ボタンクリック (onClick) → performance.now() (DOMHighResTimeStamp、ms単位float) → 直近タイムスタンプの差分計算 → 3秒以上空き → 配列リセット (新規測定として仕切り直し) → 9個目以降 → shift() で直近8間隔だけ保持 → 平均間隔 = (last - first) / (count - 1) → BPM = 60000 / 平均間隔 → 小数1位で表示
なぜperformance.now()なのか
MDN Web Docs仕様によれば、Date.now()は1ms解像度で、システムクロックの調整・NTP同期・夏時間切替で時刻が遡る可能性があります。 一方performance.now()はDOMHighResTimeStamp型でサブミリ秒精度(実装によりμs単位)、かつmonotonic clock基準で必ず単調増加します。
タップテンポでは「2つのタイムスタンプ差分」しか使わないため絶対時刻は不要。むしろシステムクロックの変動を受けないperformance.now()が必須です。
直近8間隔の移動平均で揺らぎを吸収
人間のタップは1〜30msの揺らぎがあります。1回の間隔だけでBPMを出すと:
真のBPM=120 (1拍=500ms) タップ間隔: 495ms → BPM 121.2 タップ間隔: 510ms → BPM 117.6 タップ間隔: 503ms → BPM 119.3 → 1回ごとの揺れが±2〜3BPM
本ツールは8間隔の平均を取ります(最初と最後のタイムスタンプ差を間隔数で割る)。中心極限定理により、揺らぎ±20msでも8回平均で誤差は1/√8≈0.35倍、±7ms(BPM ±1.4)に縮みます。
8タップ全間隔の平均: (t[8] - t[0]) / 8 ≈ 500ms ± 7ms BPM = 120.0 ± 1.4 → 実用上「合っている」レベル
3秒空きでの自動リセット
タップを途中で止めた後に再開すると、前回の最後のタップと新しいタップの間が異常に長くなり、平均が大幅に狂います。 本ツールは「直近タイムスタンプから3000ms以上経過したらタップ配列をリセット」する仕様で、ユーザーが意識せずとも再測定として仕切り直されます。
if (taps.length > 0 && now - taps[taps.length - 1] > 3000) {
taps.length = 0; // 配列クリア
}
taps.push(now);3秒という閾値は、BPM 20(1拍=3000ms)のギリギリ手前。これ以上長い拍は実用音楽範囲を超えるため、安全な仕切り直しタイミングとして選ばれています。
20〜400 BPMの広範囲対応
本ツールは20〜400BPMをカバーします。これは以下の実用音楽範囲を網羅:
- BPM 20: 葬送行進曲のLargo(最遅)
- BPM 60: 1拍=1秒、Adagio
- BPM 90〜110: J-POPバラード〜ミディアム
- BPM 120〜140: ポップス・EDM・ハウス
- BPM 160〜180: ドラムンベース・パンク
- BPM 200〜240: スピードメタル・ハードコア
- BPM 400: メタル32分音符のサブディビジョン
入力レイテンシの実測値
タップからperformance.now()呼び出しまでには、以下のレイテンシが乗ります:
- マウスポーリング: 8〜15ms(標準125Hz)、ゲーミング1000Hz=1ms
- OS入力ドライバ: 1〜5ms
- ブラウザイベントループ: 1〜3ms
- モニター応答時間: 5〜15ms(120Hz=8ms、60Hz=16ms)
合計15〜40msのオフセットですが、タップ間隔の差分を使うためオフセットは打ち消されます。揺らぎ(jitter)の方が問題で、ゲーミングマウス+120Hzモニターなら±5ms以内、標準環境でも±15ms程度に収まります。
さらに精度を上げる方法(将来拡張)
- 外れ値除去(中央値±2σ外を捨てる)でBPM ±0.5に到達可能
- キーボードイベント(Spaceキー)併用でマウス遅延を回避
- MIDIキーボード入力で生体ジッタを排除(Web MIDI API)
- マイク入力+オンセット検出で「曲を流すだけで自動BPM判定」
本ツールは「シンプルで信頼できる」ことを優先し、ヒューリスティック8間隔平均という古典的かつ堅牢な手法で実装しています。