マガジン

Web Audio APIで効果音を鳴らす最小コード

遊びをつくる

執筆にClaudeを使用しています。

成功、失敗、選択に応じた三種類の音を、異なる形の響きで表したイラスト。
記事のテーマをもとに制作したAI生成イラスト。

ブラウザゲームに効果音を付けようとして、<audio>タグを使ったら遅延が気になった、という経験はないでしょうか。ボタンを押した瞬間や敵を倒した瞬間に音が鳴らないと、操作感が大きく損なわれます。Web Audio APIはブラウザに標準で組み込まれた音声処理の仕組みで、ファイルなしでも音を生成でき、遅延もほぼゼロです。 この記事では「まず音を鳴らす」という一点に絞り、最小限のコードと、つまずきやすいポイントを整理します。ライブラリは使いません。HTML1ファイルに収まるコードで、コピーしてそのまま動かせる形にしています。

この記事の目次
  1. Web Audio APIが効果音に向いている理由
  2. 最小コード:ボタンを押すとビープ音が鳴る
  3. 3つのつまずきポイントと対処
  4. 音の印象を変える3つのパラメータ
  5. 音量と複数音の管理
  6. 次の行動
  7. 参考資料

Web Audio APIが効果音に向いている理由

<audio>タグはファイルを読み込んで再生する用途に適していますが、同じ音を素早く繰り返し鳴らすのが苦手です。一方、Web Audio APIはAudioContext(音声処理の土台となるオブジェクト)を軸に、音の生成・加工・出力をプログラムで制御します。

特に効果音で有効な点は次の2つです。

  • 発音タイミングの精度が高い。スケジューリングはサンプル単位で指定でき、UIイベントのコールバック内から呼んでも遅れが出にくいです。
  • ファイル不要の音を作れる。OscillatorNode(波形を生成するノード)を使うと、ピッチと波形の種類だけで「ビープ音」「ポップ音」「爆発音の断片」などを作れます。

逆に、ボイスや長いBGMはファイル再生の方が現実的です。用途によって使い分けることになります。

最小コード:ボタンを押すとビープ音が鳴る

下記が動作する最小構成です。

html
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>効果音テスト</title></head>
<body>
  <button id="btn">音を鳴らす</button>
  <script>
    const btn = document.getElementById('btn');

    btn.addEventListener('click', () => {
      // AudioContextは毎回作らず、1つを使い回す
      if (!window._ctx) {
        window._ctx = new AudioContext();
      }
      const ctx = window._ctx;

      // OscillatorNode:波形を生成する
      const osc = ctx.createOscillator();
      // GainNode:音量をコントロールする
      const gain = ctx.createGain();

      osc.type = 'square';          // 波形の種類(square / sine / triangle / sawtooth)
      osc.frequency.value = 440;    // 音の高さ(Hz)

      gain.gain.setValueAtTime(0.3, ctx.currentTime);
      // 0.1秒かけて音量をゼロに下げることで「ブツ切れ」を防ぐ
      gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.1);

      // ノードを接続する:osc → gain → スピーカー
      osc.connect(gain);
      gain.connect(ctx.destination);

      osc.start(ctx.currentTime);
      osc.stop(ctx.currentTime + 0.1);  // 0.1秒後に停止
    });
  </script>
</body>
</html>

このコードをHTMLファイルとして保存し、ブラウザで開いてボタンを押すと、短いビープ音が鳴ります。

3つのつまずきポイントと対処

AudioContextの自動再生ブロック

多くのブラウザは、ユーザー操作なしにAudioContextを開始させません。ページ読み込み時にnew AudioContext()を呼んでも、状態がsuspended(停止中)のままになります。上のコードではボタンのクリック内で初回だけ生成しているため、この制限を自然に回避しています。

もし既存のAudioContextを使い回すコードで音が鳴らない場合は、ctx.state === 'suspended'を確認し、ctx.resume()を呼ぶ必要があります。

js
if (ctx.state === 'suspended') {
  ctx.resume();
}

音がブツ切れになる

osc.stop()で即座に止めると、波形が途中で切れて「ブツッ」というノイズが出ます。上のコードのように、GainNodeでexponentialRampToValueAtTimeを使い音量をゼロに近づけてから止めるのが定番の対処です。linearRampToValueAtTimeでも構いませんが、指数カーブの方が自然に聞こえます。

なお、exponentialRampToValueAtTimeの目標値には0を指定できません(0への指数関数的変化は数学的に定義できないため)。0.001など極小の値を使います。

OscillatorNodeは使い捨て

OscillatorNodeは一度start()→stop()したあと、再びstart()することができません。効果音を鳴らすたびにcreateOscillator()で新しいノードを作る必要があります。AudioContextは使い回し、ノードは毎回生成する、という関係を押さえておくと混乱しにくいです。

音の印象を変える3つのパラメータ

ビープ音をベースに、パラメータを変えると印象が大きく変わります。

  • osc.type、変更方法:'sine' / 'square' / 'triangle' / 'sawtooth'、効果の例:sine=丸い音、square=電子音、sawtooth=ギザギザした鋭い音
  • osc.frequency.value、変更方法:数値(Hz)、効果の例:440=ラ音。低くするほど重く、高くするほど鋭くなる
  • 持続時間、変更方法:stop()の引数とgainの減衰タイミング、効果の例:短くするとクリック音、長くするとチャイム風になる

たとえばコイン取得音を作るなら、低音(300Hz)から高音(800Hz)へ周波数を変化させると「ポン↑」という上昇音になります。

js
osc.frequency.setValueAtTime(300, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(800, ctx.currentTime + 0.08);

爆発音の断片にしたい場合は、typeを'sawtooth'にして持続時間を0.05秒以下に縮め、gainの初期値を大きめ(0.6前後)に設定するとそれらしくなります。どちらも上のコードに数行足すだけで試せます。

音量と複数音の管理

ゲームが複雑になると、効果音・BGM・UIサウンドを別々に音量調整したくなります。そのときはマスターGainNodeをひとつ作り、すべての音の出力をそこに集めてからctx.destinationにつなぐ構成が管理しやすいです。

js
const masterGain = ctx.createGain();
masterGain.gain.value = 0.8;  // 全体音量
masterGain.connect(ctx.destination);

// 各効果音はmasterGainにつなぐ
gain.connect(masterGain);

この構成にしておくと、ミュートボタンや音量スライダーを後から追加するときに、masterGain.gain.valueを書き換えるだけで済みます。

次の行動

上のHTMLをローカルに保存してブラウザで開き、osc.typeとosc.frequency.valueを変えながら音を確認してみてください。気に入った音ができたら、クリックやダメージなどゲームのイベントに対応させて組み込むと、操作感の変化をすぐ体感できます。作ったゲームはアソバース(aso-verse.com)にHTMLファイルのままアップロードして公開できます。

参考資料

  • MDN Web Docs「Web Audio API」
  • MDN Web Docs「AudioContext」
  • MDN Web Docs「OscillatorNode」