録音の再生:時刻と音の対応表を順番に読む
遊びをつくる執筆にClaudeを使用しています。

「雨音のピアノ」で数フレーズ弾いて録音ボタンを止め、再生を押すと、さっき弾いた通りの間で音が鳴ります。ここで、録音されたものの正体を考えてみます。30秒ぶんの音声をそのまま保存しているのか、それとも「何秒目にどの鍵盤を弾いたか」のメモを読み返しているのか。この違いは、データの大きさ、再生の正確さ、上限の決め方にまで影響します。 この記事では、時刻と音を組にした「対応表」として録音を捉え、記録する側と再生する側の流れを順に読みます。確認できる範囲では、雨音のピアノは30秒・128音まで録音してその場で再生でき、録音は画面を開いている間だけ残ります。内部のデータ形式までは確認できていないので、公開されている仕様から「設計から読み取れること」を整理し、小さなコードで同じ仕組みを自分で組んで確かめられる形にします。
この記事の目次
録音を「音」ではなく「表」として考える
録音には大きく2通りの持ち方があります。
- 波形を保存する、中身:マイクなどから入った音そのものの連続した数値、向いている場面:声や環境音など、鳴り方を後から決められないもの
- 操作を保存する、中身:「何ミリ秒目に、どの鍵を、どの強さで」という記録、向いている場面:鍵盤のように、押した事実から音を作り直せるもの
鍵盤の演奏は、同じ楽器の同じ操作なら同じ音が鳴ります。そのため、操作だけを記録して再生時に鳴らし直す方法が成り立ちます。この表の1行が1つの音にあたり、たとえば次のような形です。数値は説明のための仮の例です。
- 0、鍵:A
- 500、鍵:S
- 1000、鍵:D
- 1180、鍵:F
再生は、この表を上から順に読み、各行の時刻になったらその鍵の音を鳴らすことです。表は時刻の昇順に並んでいるので、頭から読むだけで演奏の順序と間の取り方が再現できます。
30秒・128音という上限から読み取れること
雨音のピアノの録音には「30秒・128音まで」という上限があります。ここから読み取れることが2つあります。
1つ目は、上限が「時間」と「音の数」の2本立てになっている点です。波形をそのまま保存するなら、制限は時間(あるいはデータ量)だけで足ります。音数でも区切っているのは、音を1件ずつ数えられる単位として扱う設計と整合します。ただし、実際に表形式で持っているかどうかは、公開されている仕様だけでは断定できません。
2つ目は、どちらかに先に達したら録音が終わるという構造が自然に想定される点です。30秒の間に弾ける音数は人によって違います。ゆっくり弾けば30秒に先に達し、速い連打なら128音に先に達します。どちらの上限で止まるかは、作品を開いて弾き方を変えれば確かめられます。
また、キーボード操作(A W S E D F T G Y H U J、Z/Xで音域)で弾いた音も、画面上の鍵盤を押した音も、録音に入る点は作品を開いて確かめてください。音色(フェルトとガラス)、3段階のタッチ、余韻の切り替えが録音に反映されるかどうかも、仕様の範囲では不明です。再生時の音の違いで確かめられます。手順は後の節に書きます。
記録する側:押した瞬間に1行足す
記録の核心は単純です。録音開始の時刻を覚えておき、鍵が押されるたびに「今の時刻 − 開始時刻」と鍵の名前を表に足します。上限に達したら止めます。
次のコードは、A・S・D・Fの4キーで動く最小の録音・再生です。HTMLファイル1つで動き、外部ライブラリは使いません。音は発振器で作っています。
<button id="recBtn">録音</button>
<button id="playBtn">再生</button>
<p id="out"></p>
<script>
const ac = new AudioContext();
const FREQ = { a:261.6, s:293.7, d:329.6, f:349.2 };
const MAX_MS = 30000, MAX_NOTES = 128;
let table = [], t0 = 0, recording = false;
function tone(key, when) {
const o = ac.createOscillator(), g = ac.createGain();
o.frequency.value = FREQ[key];
g.gain.setValueAtTime(0.2, when);
g.gain.exponentialRampToValueAtTime(0.001, when + 0.6);
o.connect(g).connect(ac.destination);
o.start(when); o.stop(when + 0.6);
}
function show() {
out.textContent = table.map(n => n.ms + 'ms ' + n.key).join(' / ');
}
function stop() {
recording = false; recBtn.textContent = '録音'; show();
}
addEventListener('keydown', e => {
if (e.repeat || !(e.key in FREQ)) return;
tone(e.key, ac.currentTime);
if (!recording) return;
const ms = performance.now() - t0;
if (ms > MAX_MS || table.length >= MAX_NOTES) { stop(); return; }
table.push({ ms: Math.round(ms), key: e.key });
show();
});
recBtn.onclick = () => {
ac.resume();
if (recording) return stop();
table = []; t0 = performance.now();
recording = true; recBtn.textContent = '停止'; show();
};
playBtn.onclick = () => {
ac.resume();
const base = ac.currentTime + 0.1;
for (const n of table) tone(n.key, base + n.ms / 1000);
};
</script>読みどころは keydown の中の3行です。performance.now() - t0 で経過時間を出し、上限を超えていたら記録せずに止め、そうでなければ { ms, key } を1行足します。表の各行が「時刻と音の対応」そのものです。
なお、このコードの30秒判定は次の鍵が押された時点で行われます。最後の音から30秒を過ぎても、次に押すまで録音状態が続きます。実際の作品で時間切れをどう扱っているかは、作品を開いて確かめるところです。
再生する側:表を順に読んで予約する
再生は playBtn.onclick の3行です。表を上から読み、各行の ms を秒に直して再生開始時刻に足し、その時刻に鳴るよう音を予約します。
ここで、再生の方法にも2通りの考え方があります。
- その都度タイマーで鳴らす方法:setTimeout で各行を待って鳴らします。実装は短いものの、ほかの処理で画面が忙しいと、鳴るタイミングが少しずつずれることがあります。
- 先に全部予約する方法:上のコードのように、音声側の時計(ac.currentTime)を基準に開始時刻を渡します。再生ボタンを押した時点で全行を読み切るので、あとは音声側が時刻通りに鳴らします。
上のコードは後者です。128音までという上限があれば、一度に予約する件数が小さく抑えられ、この方法と相性がよいと言えます。ただし、雨音のピアノがどちらの方式かは、公開されている仕様からは分かりません。
自分で確かめる手順
作品を開いて、次の順で観察すると、録音の形に関する仮説を検証できます。結果は読者自身の環境で確認してください。
- 間の再現:短いフレーズを、わざと間を不揃いにして(速い2音のあとに長い休み、など)録音し、再生します。弾いた通りの間が再現されるかを聞き比べます。表に時刻があれば再現されるはずですが、等間隔に直されるのかどうかは自分の耳で確かめます。
- 上限の確認(音数):速い連打で録音し続け、128音のところで何が起きるかを見ます。129音目を弾いたとき、録音が止まるのか、無視されるのかを記録します。
- 上限の確認(時間):音数が少ないまま、ゆっくり弾いて30秒を超えたときの挙動を見ます。時計を横に置くと確認しやすくなります。
- 設定の反映:音色、タッチ、余韻のうち1つだけを変えて録音し、再生中に設定を別の値へ戻して聞きます。録音時の設定で鳴るのか、再生時の設定で鳴るのかが分かります。1回に変える条件は1つだけにしてください。
- 保存範囲:録音したあとに画面を再読み込みして、録音が残っているか見ます。「画面を開いている間だけ残る」という仕様の確認です。
記録するときは「操作/聞こえた結果」を一行ずつメモにすると、後で上限や設定の扱いを整理できます。
起こりやすい誤り
自作する場合、次の点でつまずきやすくなります。
- 時刻の基準を混ぜる:録音は performance.now()、再生は ac.currentTime と、基準が違う時計を使っています。録音側では「開始からの差」だけを保存し、再生側で再生開始時刻に足すと、基準の違いが問題になりません。絶対時刻を表に入れないことが要点です。
- キーの繰り返し入力を拾う:押しっぱなしで keydown が連続して発生します。コード中の e.repeat の除外がないと、1回の押下が何件もの音として記録されます。
- 再生の初期化を忘れる:ブラウザは、ユーザー操作の前に音を鳴らせないことがあります。ボタンの中で ac.resume() を呼んでおくと安全です。
- 表の途中を上書きする:録音を開始するたびに table = [] で空にしないと、前回の続きに書き足されます。
- 上限の判定位置:記録してから数えると、上限ちょうどで1件多く入ることがあります。足す前に数えると、128件で止まります。
次の行動
まず「雨音のピアノ」で、間を不揃いにした短いフレーズを録音して再生し、間の再現と上限の挙動を確認してください。そのあと上のコードをHTMLファイルとして保存して、鍵や上限の値を変えながら、表の1行ずつがどう音になるかを手元で追ってみてください。自作ゲームに録音機能を足すなら、「時刻と音の表」を1つ持つところから始められます。
参考資料
- MDN Web Docs「Web Audio API」
- MDN Web Docs「Performance.now()」

