手軽屋
ツール一覧

タップテンポ測定の精度を上げる仕組み: 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をカバーします。これは以下の実用音楽範囲を網羅:

入力レイテンシの実測値

タップからperformance.now()呼び出しまでには、以下のレイテンシが乗ります:

合計15〜40msのオフセットですが、タップ間隔の差分を使うためオフセットは打ち消されます。揺らぎ(jitter)の方が問題で、ゲーミングマウス+120Hzモニターなら±5ms以内、標準環境でも±15ms程度に収まります。

さらに精度を上げる方法(将来拡張)

本ツールは「シンプルで信頼できる」ことを優先し、ヒューリスティック8間隔平均という古典的かつ堅牢な手法で実装しています。

関連