音の遅れは何ミリ秒から気づくか:自分の耳で測る
遊びをつくる執筆にClaudeを使用しています。

キーを押してから音が鳴るまでが何ミリ秒なら「遅い」と感じるのかは、実は一つの数字では答えられません。耳の個人差に加え、使うイヤホンやスピーカー、ブラウザ、OS、鳴らす音の種類でも変わります。他人の数字を借りるより、自分の環境で自分の耳の境目を測るほうが、ゲームを作るときには役に立ちます。 この記事は、その測り方の設計書です。キーを押す瞬間から音を鳴らすまでの間に、遅れを少しずつ足していきます。「あれ、遅れたかも」と感じた値を記録し、次に順番をランダムにして、その感覚が当て推量でないかを確かめます。結果は書きません。やり終えると、自分の「気づく境目」を表す数行の記録が手元に残ります。 測れるのは、環境がもともと持つ遅れの上に足した分です。この点が、読み方を決める最初の前提になります。
測っているのは「足した遅れ」の境目
キーを押してから耳に音が届くまでには、キーボードの検出、ブラウザのイベント処理、音の生成、OSの音声処理、出力機器までの経路があり、それぞれが少しずつ時間を使います。Bluetoothのイヤホンのような無線機器では、この経路が長くなる傾向があります。
今回のプログラムは、このうち「音を鳴らす時刻」だけを指定どおりに後ろへずらします。つまり、記録される「0 ms」は遅れがゼロという意味ではありません。自分の環境が元から持っている遅れだけの状態です。
この区別が大切なのは、結果を2通りに読めるからです。
- 「足した遅れが○○ msを超えると気づく」は、この環境での境目として読めます。
- 「人間は○○ msから気づく」とは読めません。環境が違えば元の遅れも違うためです。
ブラウザによっては、出力側の遅れの目安を返す値(Web Audio APIの outputLatency)が取れる場合があります。開発者ツールのコンソールで、後述のページを開いたあとに ctx.outputLatency と入力してみてください。値が返らない環境もあるので、取れたら記録欄に書き添える程度の扱いにします。
準備と手順
準備するもの
- パソコンと物理キーボード(タッチ画面では入力側の条件が変わるので、別の実験として分けます)
- いつも作品を遊ぶときのヘッドホン、イヤホン、スピーカーのうち、測りたいもの一つ
- 静かな部屋、一定の音量
- 記録用の紙か表計算ソフト
- 下のHTMLを保存したファイル(例:delay.html)。ブラウザで開くだけで動き、外部ライブラリは使いません
<!doctype html>
<meta charset="utf-8">
<title>音の遅れテスト</title>
<p id="m"></p><p id="o"></p>
<script>
const ASC = false; // true: 昇順・遅れを表示 / false: ランダム・非表示
const steps = [0, 10, 20, 30, 50, 80, 120, 200];
const reps = ASC ? 1 : 3;
const trials = [];
const log = [];
let i = 0, ctx;
for (const s of steps) for (let k = 0; k < reps; k++) trials.push(s);
if (!ASC) for (let k = trials.length - 1; k > 0; k--) {
const r = Math.floor(Math.random() * (k + 1));
[trials[k], trials[r]] = [trials[r], trials[k]];
}
const m = document.getElementById("m");
const o = document.getElementById("o");
function beep(ms) {
ctx = ctx || new AudioContext();
const t = ctx.currentTime + ms / 1000;
const osc = ctx.createOscillator();
const g = ctx.createGain();
osc.type = "triangle";
osc.frequency.value = 440;
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(0.4, t + 0.005);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
osc.connect(g).connect(ctx.destination);
osc.start(t);
osc.stop(t + 0.2);
}
function show() {
if (i < trials.length) {
m.textContent = (i + 1) + " / " + trials.length + (ASC ? " 遅れ " + trials[i] + " ms" : "");
return;
}
m.textContent = "終了";
o.textContent = steps.map(s => { const r = log.filter(x => x.ms === s);
return s + " ms: 遅れありと答えた " + r.filter(x => x.said).length + " / " + r.length; }).join(" | ");
}
addEventListener("keydown", e => {
if (e.repeat || i >= trials.length) return;
if (e.code === "Space") { e.preventDefault(); beep(trials[i]); }
else if (e.key === "b") beep(0);
else if (e.key === "j" || e.key === "f") { log.push({ ms: trials[i], said: e.key === "j" }); i++; show(); }
});
show();
</script>操作は3つだけです。
- スペースキー:現在の試行の音を鳴らします。
- Jキー:「遅れていた」と回答して次の試行へ進みます。
- Fキー:「遅れていない」と回答して次の試行へ進みます。
Bキーは、遅れを足さない基準音を鳴らします。回答は記録されず、進行もしません。
手順1:上昇法で目安をつける
最初は ASC を true にして開きます。8段階(0、10、20、30、50、80、120、200 ms)が小さい順に1回ずつ並び、画面に現在の遅れが出ます。
- Bキーを5回ほど押して、基準音に耳を慣らします。最初の再生は準備の都合で挙動が違うことがあるため、練習として捨てます。
- 試行ごとにスペースキーを1回だけ押します。聞き直しはしません。
- 「遅れていた」と感じたらJ、感じなければFを押します。
- 最後まで進むと、段階ごとの回答が表示されます。最初にJを押した段階をメモします。
上昇法の長所は、境目のおよその位置が数分でつかめることです。短所は、遅れが増えていくと自分で知っているため、期待が回答に混ざることです。この段階の数字は、あくまで目安として扱います。
手順2:ランダム順の盲検で確かめる
次に ASC を false にして開き直します。各段階が3回ずつ、順番をシャッフルして並びます。画面には何問目かだけが出て、今の遅れは見えません。操作は手順1と同じです。
- 迷ったときは、回答を保留せずどちらかに決めます。
- 目を閉じるか画面から視線を外し、画面の変化を手がかりにしないようにします。
- 24回の試行が終わったら、集計をそのまま記録します。
この方法は、上昇法で出た目安が「本当に聞き分けた結果」なのか「期待による思い込み」なのかを切り分けるためのものです。
記録の取り方と結果の読み方
記録は、次の表のように「段階ごとに何回中何回、遅れありと答えたか」で残します。表の上には、条件を一行ずつ書き添えておきます。
- 日付と時刻
- 機器(キーボードの種類、有線か無線か、イヤホンやスピーカーの種類)
- ブラウザとOS
- 音量の設定
- 取れた場合は outputLatency の値
- 0、試行数:3、「遅れあり」と答えた数:、割合:
- 10、試行数:3、「遅れあり」と答えた数:、割合:
- 20、試行数:3、「遅れあり」と答えた数:、割合:
- 30、試行数:3、「遅れあり」と答えた数:、割合:
- 50、試行数:3、「遅れあり」と答えた数:、割合:
- 80、試行数:3、「遅れあり」と答えた数:、割合:
- 120、試行数:3、「遅れあり」と答えた数:、割合:
- 200、試行数:3、「遅れあり」と答えた数:、割合:
読み方は次の順に進めます。
- 0 msの行を最初に見ます。 遅れを足していないのに「あり」と答えた回数が、この実験での勘違いの量です。ここが多いなら、自分の判断基準が不安定ということです。他の段階の読み取りも慎重にします。
- 段階が上がるにつれて「あり」が増えているかを見ます。 増えていれば、境目を読み取れる状態です。どの段階でも半々に近ければ、その範囲では聞き分けられていないと読めます。
- 境目の候補を決めます。 「あり」が3回中2回以上になり、それより大きい段階でもその状態が続く、最小の段階を候補とします。80 msで多いのに120 msで少ないなど、順序が崩れる場合は、ばらつきが大きいと読みます。
- 境目の前後を細かく測り直します。 たとえば候補が30 msと50 msの間にあるなら、steps を [0, 35, 40, 45, 50] のように書き換え、reps を5〜10に増やして、もう一度ランダム順で測ります。3回では偶然の影響が大きいためです。
- 条件を変えて繰り返します。 有線と無線、スピーカーとイヤホンなど、環境を一つだけ変えて同じ手順をやり直し、境目が動くかを比べます。
上昇法の目安とランダム順の結果が近ければ、その値を自分の目安として使えます。大きくずれるなら、上昇法は期待に引きずられていた可能性が高く、ランダム順の結果のほうを信頼します。
誤りやすい点とゲームへの戻し方
起こりやすい誤りを挙げます。
- 押し直して聞き比べる。 1試行で複数回鳴らすと、2回目以降は音のタイミングを予想して聞くことになります。1試行1回を守ります。
- 疲れた状態で続ける。 24回を一気にやると集中が落ちます。途中で休むか、日を分けます。
- 別の環境の結果を混ぜる。 イヤホンを替えたり、無線から有線に替えたりした記録は、別の表にします。
- 0 msを「遅れなし」と信じる。 先に述べたとおり、環境の元の遅れが含まれています。
- 音量や音色を変える。 立ち上がりの鋭い音は遅れが目立ちやすく、柔らかい音は目立ちにくいといった違いが出えます。比べるなら、音を固定してから環境を変えます。
- 画面の変化と混同する。 画面を見ていると、視覚の変化と音のずれが判断に混ざります。音だけで判断します。
測り終えたら、結果をゲームの設計に結びつけます。
- 自分の境目は、その環境での一つの目安にすぎません。他の人は違う可能性があるので、遊ぶ人の機器をそのまま再現できないという前提で、遅れの余裕を見込みます。
- 押してから鳴る経路のうち、自分で減らせるのは次のような部分です。効果音は事前に読み込んでおく、音の仕組みはユーザーの最初の操作で起動しておく、再生のたびに新しい仕組みを作らない、などです。
- 遅れの影響が大きいのは、打鍵と音が直結するリズムや演奏系です。物語を読み進めるゲームなどでは、許容できる幅が広いことが多いでしょう。作ろうとしているゲームがどちらに近いかを、判断の基準に加えます。
自分の境目をつかんだあとは、アソバースの公式作品で、押したときの手応えを確かめてみる方法もあります。「雨音のピアノ」はキーボード(A W S E D F T G Y H U J)で弾く作品で、「WAVE / WAVE」はパッドの演奏を録音できる作品です。どちらもログインなしで遊べます。遅れの感じ方は機器しだいなので、この実験と同じ機器で試して、自分の測定値とどう結びつくかは作品を開いて確かめてください。
次の行動
まず delay.html を作り、Bキーで耳を慣らしてから、ASC = true の上昇法で一回りしてみてください。そのあと ASC = false に変えて24回のランダム順を行い、0 msの行と境目の候補を表に残します。環境を一つ変えて同じ手順を繰り返すと、自分のゲームに必要な遅れの予算が見えてきます。
参考資料
- MDN Web Docs「Web Audio API」
- W3C「Web Audio API」仕様



