# サウンドの設計

webg では、ブラウザの標準機能である Web Audio API を用いてサウンドを制御します。まず、Web Audio API を用いてサイン波の単音を1秒間鳴らす最もシンプルな実装例を見てみましょう。

```html
<head>
  <meta charset="UTF-8">
  <title>単音を鳴らす</title>
</head>
<body>
  <button id="playButton">単音を鳴らす</button>
  <script>
    const playButton = document.getElementById("playButton");
    let audioContext = null;
    let osc = null;

    const startAudio = () => {
      audioContext = new AudioContext();           // AudioContext を作成
      osc = audioContext.createOscillator();       // 音源 OscillatorNode を作成
      const gainNode = audioContext.createGain();  // 音量調整用 GainNode を作成
      osc.type = "sine";                           // 正弦波を指定
      osc.frequency.value = 220;                   // 周波数を指定 A4 = 220Hz
      gainNode.gain.value = 0.2;                   // 音量を指定
      osc.connect(gainNode);                       // 接続する
      gainNode.connect(audioContext.destination);
    }

    const playTone = () => {
      osc.start();                                 // 音を鳴らす
      osc.stop(audioContext.currentTime + 1);      // 1秒後に止める
    }

    playButton.addEventListener("click", () => {
      startAudio();
      playTone();
    });
  </script>
</body>
</html>
```

このように、単に音を鳴らすだけであれば Web Audio API を直接操作することで比較的簡単に実現できます。しかし、実際のアプリケーション開発において、単発の効果音を鳴らすだけで十分なケースは稀です。効果音（SE）とバックグラウンドミュージック（BGM）では、音響的な目的が根本的に異なります。また、音量、ディレイ、リバーブ、そしてメロディの管理をそれぞれ独立して制御したいという要求が必ず発生します。

そこで webg では、これらの要求を効率的に満たすため、機能を `ToneSynth`、`AudioSynth`、`GameAudioSynth` の3つの階層に分けて設計しています。`ToneSynth` は単音の再生・停止およびエフェクトを担う最小の基盤であり、`AudioSynth` はその上に SE と BGM のバス分離およびシーケンサ機能を追加します。そして `GameAudioSynth` は、これらを統合し、プリセット名で直感的に操作できるインターフェースを提供します。

この設計の核心は、「何を鳴らすか」というコンテンツ側の視点と、「どう聞かせるか」という音響設計側の視点を明確に分離している点にあります。また、Web Audio API 特有の制約である `AudioContext` の遅延初期化への対応や、系統別のエフェクト調整などを一貫したフローで管理することで、開発者が配線処理に煩わされることなく音響演出に集中できる環境を構築しています。

## 音響設計の思想とオーディオグラフ

Web Audio API を理解する上で最も重要な概念が、音の流れを「オーディオグラフ」として組み立てるという考え方です。これは、音源から最終出力までを、ノード（Node）と呼ばれる小さな部品の接続として表現するものです。物理的な機材で例えるなら、ギターからエフェクターを経てミキサーに接続し、最終的にスピーカーから出力させる構成に似ています。

基本的には、`[音源]` $\rightarrow$ `[加工]` $\rightarrow$ `[出力]` という一本の流れになります。例えば、オシレーターで音を作り、音量を調整し、リバーブを通して出力する場合、概念的には次のような接続になります。

`[OscillatorNode]` $\rightarrow$ `[GainNode]` $\rightarrow$ `[ConvolverNode]` $\rightarrow$ `[AudioDestinationNode]`

ここで、`OscillatorNode` は波形を発生させるソースノードであり、`GainNode` や `ConvolverNode` は音量や残響などの性質を変える加工ノード、`AudioDestinationNode` は最終的な出力先となります。

しかし、実際のアプリケーションでは「SE だけ音量を上げたい」「BGM だけディレイを変えたい」といった個別の制御が求められます。これを個々の音源に対して行うと管理が極めて複雑になるため、ここで「バス（Bus）」という概念を導入します。バスとは、複数の音をいったんまとめて流すための共通経路のことです。SE 用の音を SE バスへ、BGM 用の音を BGM バスへ集約させれば、経路上のノードを一つ調整するだけで、その系統全体の音量や空間特性を一括して制御できます。

webg のサウンド基盤はこのオーディオグラフの構築を自動化しています。`ToneSynth.ensureContext()` で `AudioContext` とマスターバスを生成し、`buildToneFxChain()` でディレイやリバーブを含むエフェクトチェーンを構築します。`AudioSynth` はこの基盤を継承し、さらに BGM 専用のバスを追加することで、SE と BGM の完全な経路分離を実現しています。

このように、`ToneSynth` は低レイヤー（ローレベル）な「単音用の配線ボード」に相当し、`AudioSynth` はそれを統合する「ミキシングコンソール」、そして `GameAudioSynth` はそれらを論理的な名前で操作する「高レイヤー（ハイレベル）」なインターフェースとして機能します。

## サウンド機能の役割分担

サウンド機能の階層構造とそれぞれの責任範囲は以下の通りです。

1.  **`ToneSynth` (単音基盤層)**
    単音の再生・停止、ADSRエンベロープ制御、ディレイ・リバーブ、およびインパルスレスポンスの動的生成を担当します。「1つの音をどのように鳴らし、どのように止めるか」という最小単位を管理する低レイヤー（ローレベル）な層です。
2.  **`AudioSynth` (SE/BGM 基盤層)**
    `ToneSynth` を継承し、SE バスと BGM バスの分離、BGM シーケンサ、メロディ登録、BGM 専用エンベロープを管理します。単音基盤をアプリケーション全体の音構成へと拡張する役割を担います。
3.  **`GameAudioSynth` (高レイヤー/インターフェース層)**
    `AudioSynth` の上に構築され、ゲーム向けのメロディプリセットや SE カタログ、便利な発音ヘルパーを提供します。「どの名前の音を鳴らすか」という論理的な管理を行う高レイヤー（ハイレベル）な層です。
4.  **`webg/samples/sound` (検証層)**
    これらの機能を実際の UI を通じて調整し、音響特性を確認するためのサンプル実装です。

この階層構造により、単発の SE と連続的な BGM という異なる性質の音を適切に分離しつつ、開発者はコアな配線処理を意識せずに、プリセットの切り替えやパラメータ調整だけで演出を行えるようになっています。

## 標準的な利用フロー

### オーディオコンテキストの有効化

Web Audio API を利用する際、最も注意すべき点は、音声再生は必ず「ユーザーの操作後」に行わなければならないというブラウザのオートプレイポリシーです。

このため、`ToneSynth` および `AudioSynth` はコンストラクタで即座に `AudioContext` を生成しません。ユーザーがボタンを押すなどの操作を行い、`resume()` が呼ばれたタイミングで初めて `ensureContext()` が動作し、マスターバスや各系統のルーティングが構築されます。`webg/samples/sound` に「Audio Start」ボタンが用意されているのは、この制約を適切に処理するためです。

### SE と BGM の使い分けと空間調整

音声機能が有効化した後は、SE と BGM を別々のバスとして制御します。特に BGM は単純なファイルの再生ではなく、`scheduleBgm()` による先読みスケジューリングで進行するため、BPM やメロディの変更が楽曲全体の流れに動的に反映されます。

空間演出におけるディレイとリバーブの調整では、「量」と「特性」を明確に区別して制御します。
- **量（Amount）:** `setSeReverb()` や `setBgmReverb()` などで調整します。これはセンド/リターン量、つまり「どれだけリバーブ成分を混ぜるか」を決定します。
- **特性（Characteristic）:** `setSeReverbImpulse()` や `setBgmReverbImpulse()` で調整します。これはコンボリューションリバーブに用いるインパルスレスポンス（IR）を変更し、「部屋（room）」「ホール（hall）」「プレート（plate）」といった空間の質や残響時間を決定します。

量と特性を分けて制御することで、音量バランスを維持したまま空間の広がりだけを変えるといった、精密な音響設計が可能になります。

## 使い方の基本例

### 効果音（SE）の再生

通常は `GameAudioSynth` を使用します。オーディオを有効化し、プリセット名で SE を再生する最小の実装例は以下の通りです。

```js
import GameAudioSynth from "./webg/GameAudioSynth.js";

const synth = new GameAudioSynth();

// ユーザー操作（クリックなど）をきっかけにオーディオを有効化して再生する
button.addEventListener("click", async () => {
  // AudioContextを有効化し、ルーティングを構築する
  await synth.resume();
  
  // 全体の音量を設定
  synth.setMasterVolume(0.25);
  
  // プリセット名でSEを再生
  synth.playSe("poyoon");
});
```

### 音量バランスの調整

マスターボリュームを基準とし、SE と BGM の相対的なバランスを個別に設定します。

```js
synth.setMasterVolume(0.25); // 全体の基準音量
synth.setSeVolume(0.90);    // SEの相対音量
synth.setBgmVolume(0.75);   // BGMの相対音量
```

### 空間エフェクト（ディレイ・リバーブ）の調整

ディレイとリバーブの「量」を調整し、音の奥行きや空間の広がりを制御します。

```js
// SEディレイ: 時間, フィードバック, ウェット量
synth.setSeDelay(0.11, 0.26, 0.22);
// BGMディレイ: 時間, フィードバック, ウェット量
synth.setBgmDelay(0.18, 0.22, 0.18);

// SEリバーブ: センド量, リターン量
synth.setSeReverb(0.32, 0.55);
// BGMリバーブ: センド量, リターン量
synth.setBgmReverb(0.28, 0.48);
```

### リバーブ特性の変更

インパルスレスポンス（IR）を変更して、空間の質（部屋の大きさや材質など）を切り替えます。

```js
// SEのリバーブ特性を「ルーム」に設定
synth.setSeReverbImpulse({
  kind: "room",
  durationSec: 2.2,
  decay: 1.6
});

// BGMのリバーブ特性を「ホール」に設定
synth.setBgmReverbImpulse({
  kind: "hall",
  durationSec: 4.0,
  decay: 1.9
});
```

### エンベロープの調整

音の立ち上がり（アタック）や減衰（ディケイ）を調整します。SE は輪郭をはっきりさせ、BGM は持続感を出すといった使い分けが可能です。

```js
// SE用のエンベローププリセットを適用
synth.setSeEnvelopePreset("piano", {
  attack: 0.005,
  decay: 0.05,
  sustain: 0.55,
  release: 0.08
});

// BGM用のエンベロープを個別に設定
synth.setBgmEnvelope({
  attack: 0.03,
  decay: 0.20,
  sustain: 0.60,
  release: 0.40
});
```

### BGMプリセットの切り替え

`GameAudioSynth` に内蔵されているメロディプリセットを `setMelody()` で切り替えます。BPM や root、BGM envelope も同時に変更したい場合は、以下のようにプロファイルテーブルを用いて管理すると効率的です。

```js
const bgmProfiles = {
  boss: {
    melody: "boss_alert",
    bpm: 144,
    rootHz: 196.0,
    envelope: { attack: 0.02, decay: 0.16, sustain: 0.56, release: 0.32 }
  }
};

const applyBgmProfile = (synth, name) => {
  const profile = bgmProfiles[name];
  synth.setMelody(profile.melody);
  synth.setBpm(profile.bpm);
  synth.setRootHz(profile.rootHz);
  synth.setBgmEnvelope(profile.envelope);
};

// メロディ preset を直接切り替えて開始
synth.setMelody("minor_drive");
synth.startBgm();

// 場面に応じた profile をアプリ側で適用
applyBgmProfile(synth, "boss");
```

## 独自BGMをアプリ側で追加する

`GameAudioSynth` は既存のプリセットを切り替えるだけでなく、`registerMelody()` を用いてアプリケーション側から独自の BGM を追加することが可能です。これにより、ライブラリ本体を書き換えることなく、ステージ曲やボス戦専用のループ曲などを定義できます。

webg の BGM は、16ステップを1小節とする簡易シーケンサで構成されています。各ステップは8分音符単位で進行し、ベースとリードが個別に予約再生されます。

以下は、アプリ側で `story_field` という新しい BGM を追加して利用する例です。

```js
import GameAudioSynth from "./webg/GameAudioSynth.js";

const synth = new GameAudioSynth();

async function startStoryFieldBgm() {
  await synth.resume();

  synth.registerMelody("story_field", {
    scale: [0, 2, 4, 5, 7, 9, 11],
    bassPattern: [0, -5, -3, -2, 0, -4, -2, -5],
    bassOctave: -12,
    leadDegrees: [
      0, 2, 4, 5, 7, 5, 4, 2,
      0, 2, 4, 7, 9, 7, 5, 4,
      0, 2, 4, 5, 7, 9, 7, 5,
      4, 5, 7, 9, 11, 9, 7, 5
    ],
    leadHoldSteps: [
      1.0, 1.0, 1.0, 1.6, 1.0, 1.0, 1.0, 2.0,
      1.0, 1.0, 1.0, 1.7, 1.0, 1.0, 1.0, 2.4,
      1.0, 1.0, 1.0, 1.6, 1.0, 1.0, 1.0, 2.0,
      1.0, 1.0, 1.0, 1.8, 1.0, 1.0, 1.0, 2.8
    ],
    leadOctave: 12,
    leadType: "triangle",
    leadDur: 0.11,
    leadGain: 0.060,
    bassType: "triangle",
    bassDur: 0.16,
    bassGain: 0.074,
    rhythmDropWeakProb: 0.28,
    rhythmDropTailProb: 0.52
  });

  synth.setBpm(116);
  synth.setRootHz(220.0);
  synth.setBgmEnvelope({
    attack: 0.03,
    decay: 0.18,
    sustain: 0.60,
    release: 0.36
  });
  synth.setMelody("story_field");
  synth.startBgm();
}
```

`leadDegrees` の要素数を増やすことで、小節をまたいだ長いフレーズを作成できます。また、`bassPattern` を 8 要素に伸ばせば、2 小節周期の低音進行を構築することが可能です。

### BGM パラメータの定義

独自 BGM を作成する際の主要なパラメータの役割は以下の通りです。

- `scale`: 度数を半音へ変換する基準です。`[0, 2, 3, 5, 7, 8, 10]` ならマイナー系、`[0, 2, 4, 5, 7, 9, 11]` ならメジャー系に近い響きになります。
- `bassPattern`: ベースの進行です。1 要素が 4 ステップ分割り当てられるため、4 要素で 1 小節、8 要素で 2 小節分の進行になります。
- `bassOctave`: ベース全体のピッチを調整します。`-12` は 1 オクターブ下を指定します。
- `leadDegrees`: リード旋律を定義する配列です。`null` を指定したステップは休符となります。
- `leadGate` と `leadDegreeStep`: `leadDegrees` を記述せず、一定の規則で旋律を生成したい場合の簡易指定です。
- `leadHoldSteps`: 各ステップの音価倍率です。フレーズの終端や強拍で音を伸ばす際に使用します。
- `leadOctave`: リード全体の高さを指定します。
- `leadType` と `bassType`: オシレーターの波形です。音色の印象を決定づけます。
- `leadDur` と `bassDur`: 各音の基本持続時間です。
- `leadGain` と `bassGain`: 各パートの音量バランスを調整します。
- `rhythmDropWeakProb`: 弱拍を確率的に休符にする設定です。値を高くすると軽快なグルーヴになります。
- `rhythmDropTailProb`: 小節末尾付近を確率的に休符にする設定です。値を高くすると「息継ぎ」のような間が生まれます。

なお、`registerMelody()` では `leadHoldSteps`、`rhythmDropWeakProb`、`rhythmDropTailProb` の指定が必須です。不完全なデータによる曖昧な再生を避けるため、不足している場合は例外が発生します。

## 波形の選択と音色の調整

`OscillatorNode` を用いたサウンド生成では、波形の選択が音色の印象を決定します。

- `sine` (正弦波): 倍音が少なく、丸く柔らかい音です。静かな BGM や背景的な SE に適しています。
- `triangle` (三角波): `sine` より芯がありつつも柔らかい音です。BGM のリードや汎用的な UI 音に適しています。
- `square` (矩形波): 輪郭が強く、レトロゲームらしい主張のある音です。メロディを強調したい場合や通知音に適しています。
- `sawtooth` (鋸歯波): 倍音が非常に多く、強く荒い印象の音です。警告音やボス戦 BGM、派手な SE に適しています。

波形の変更は、BGM であれば `leadType` や `bassType`、SE であれば `playTone()` や `playGameTone()` の `options.type` で指定します。

## エンベロープと主要パラメータの制御

音の立ち上がりや余韻などの時間的変化は、エンベロープと各種パラメータで制御します。

- `attack`: 音量が 0 からピークに達するまでの時間です。
- `decay`: ピークから持続音量（Sustain）まで減衰する時間です。
- `sustain`: 減衰後に維持される音量比です。
- `release`: ノート終了後に音が消えるまでの時間です。
- `gain`: 個別の音量です。ミックス全体の音量とは別に設定します。
- `dur`: ノートの基本持続時間です。
- `pan`: 左右の定位（パンニング）です。`-1`（左）から `1`（右）で指定します。
- `detune`: セント単位の微調整です。わずかにずらすことで、音の厚みや揺らぎを表現できます。
- `when`: `AudioContext` 上の絶対時刻を指定します。複数の音を精密にずらして重ねる際に使用します。

## 独自の効果音をアプリ側で構築する

独自の SE を作成する場合、必ずしも `GameAudioSynth` のプリセットに追加する必要はありません。アプリ側で関数を定義し、`playTone()` または `playGameTone()` を組み合わせることで、柔軟に音を構築できます。

`playTone()` は波形やエンベロープをすべて個別に指定できる低レイヤー（ローレベル）なメソッドです。一方、`playGameTone()` は `percussion` や `piano` といった楽器カテゴリのプリセットを選択し、必要な項目だけを上書きできる高レイヤー（ハイレベル）なヘルパーです。

以下は、宝箱が開いた際の演出音を独自に構築する例です。

```js
import GameAudioSynth from "./webg/GameAudioSynth.js";

const synth = new GameAudioSynth();

function playTreasureOpen() {
  const t0 = synth.ctx.currentTime;

  // 最初の明るい立ち上がり
  synth.playGameTone(523.25, 0.08, "guitar", {
    when: t0,
    type: "triangle",
    gain: 0.07,
    release: 0.06,
    pan: -0.08
  });

  // 少し遅れて和音感を足す
  synth.playGameTone(783.99, 0.12, "piano", {
    when: t0 + 0.040,
    type: "triangle",
    gain: 0.06,
    release: 0.10,
    pan: 0.06
  });

  // 高域のきらめき
  synth.playGameTone(1174.66, 0.16, "woodwind", {
    when: t0 + 0.090,
    type: "sine",
    gain: 0.04,
    release: 0.16,
    pan: 0.20
  });
}

button.addEventListener("click", async () => {
  await synth.resume();
  playTreasureOpen();
});
```

単一の音ではなく、数十ミリ秒の差を設けて複数の音を重ね合わせることで、単純なビープ音を避け、奥行きのあるサウンドを構築できます。

### 名前付き SE カタログによる管理

アプリケーションの規模が大きくなった場合は、SE の対応表をアプリ側で管理することを推奨します。これにより、ゲームロジックと具体的な音色定義を分離できます。

```js
const customSe = {
  treasure_open: () => playTreasureOpen(),
  portal_start: () => playPortalStart(),
  dialogue_tick: () => playDialogueTick()
};

function playAppSe(name) {
  const se = customSe[name];
  if (!se) {
    throw new Error(`Unknown app sound effect: ${name}`);
  }
  se();
}
```

## 音声ファイル（AudioBuffer）の利用

合成音ではなく、mp3、wav、ogg などの音声ファイルを使用したい場合は、`AudioSynth` の `AudioBuffer` 管理機能を利用します。

`AudioSynth.loadAudioBuffer(name, url)` を用いてファイルを読み込み、`playAudioBuffer(name, options)` で再生します。このとき、`options.bus` に `"se"` または `"bgm"` を指定することで、それぞれのバスに設定された音量やエフェクト（ディレイ・リバーブ）を適用させることができます。

```js
import AudioSynth from "./webg/AudioSynth.js";

const synth = new AudioSynth();

button.addEventListener("click", async () => {
  await synth.resume();

  // mp3 を読み込み、SE bus へ流して再生
  await synth.loadAudioBuffer("door", "./audio/door.mp3");
  synth.playAudioBuffer("door", {
    bus: "se",
    gain: 0.8,
    pan: -0.15
  });
});
```

環境音などのループ素材を扱う場合は、`bus: "bgm"` と `loop: true` を指定します。再生中の `voice` ハンドルを保持していれば、`voice.stop()` や `stopAudioBuffer(voice)` を呼び出して停止させることができ、`fadeSec` を指定することで自然なフェードアウトが可能です。

```js
await synth.loadAudioBuffer("forest", "./audio/forest_loop.ogg");
const voice = synth.playAudioBuffer("forest", {
  bus: "bgm",
  loop: true,
  gain: 0.45
});

// シーン終了時に短くフェードアウト
voice.stop(synth.ctx.currentTime, { fadeSec: 0.4 });
```

素材の読み込み失敗や未登録名の再生要求は、設定ミスを早期に発見するため、あえて例外として処理されます。

## `AudioSynth` の内部構造

### バス構成とルーティング

`AudioSynth` は音響基盤としての役割を担います。親クラスである `ToneSynth` の `ensureContext()` および `resume()` による遅延初期化を起点とし、マスターバス $\rightarrow$ SE バス $\rightarrow$ 各エフェクトチェーンというルーティングを構築します。

ここに BGM バスと BGM シーケンサを追加することで、SE と BGM の完全な分離を実現しています。単発音の生成と停止は `ToneSynth` が受け持ち、`AudioSynth` は `playSe()`、`AudioBuffer` の再生、メロディデータに基づく先読みスケジューリング、および BGM 専用エンベロープの管理を担当します。

### インパルスレスポンスの動的生成

`ToneSynth.createImpulseResponse()` は、外部ファイルを読み込まずに数学的な計算によってステレオの `AudioBuffer` を生成します。

1.  **プロファイルの取得:** `getImpulseProfile(kind)` により、「room」「hall」「plate」それぞれの反射密度や減衰率を決定します。
2.  **テールの書き込み:** `writeImpulseTail()` で、時間経過とともに高域が減衰するノイズ列を生成し、残響の尾（テール）を作成します。
3.  **初期反射の追加:** `addEarlyReflections()` で、左右チャンネルに時間差を持たせた初期反射を加えます。これにより、空間の「広さ」や「方向感」をシミュレートします。
4.  **正規化:** `normalizeImpulse()` でピーク音量を揃え、特性を切り替えても極端な音量差が出ないように調整します。

### BGM シーケンサの仕組み

webg の BGM は、スコア（楽譜）に基づいた予約再生方式を採用しています。`startBgm()` が処理を開始し、`scheduleBgm(lookAheadSec)` が少し先の未来までのノートを予約し、`scheduleBgmStep(step, when)` がベースとリードの音を組み立てます。

メロディプリセットは、単なる音階の並びではなく、スケール、ベースパターン、リードのゲート時間、音色タイプなどのパラメータセットとして定義されています。リードの音域を広く取り、ベースパターンを多ステップにすることで、単調な反復を避け、音楽的な展開を実現しています。

## `GameAudioSynth` の役割

`GameAudioSynth` は `AudioSynth` の高レイヤーラッパーであり、ゲーム開発者が即座に利用できるメロディプリセットと SE カタログを提供します。

アプリケーション開発においては、このクラスをエントリーポイントとして利用し、「どの場面でどのメロディや BPM を使用するか」といった対応表をアプリ側に持たせることで、ゲームロジックと音響設定をシンプルに接続できます。

## `webg/samples/sound` による検証

`webg/samples/sound` は、音響設計の調整を効率的に行うための検証ツールです。`GameAudioSynth` および `AudioSynth` のパラメータをリアルタイムに操作し、その結果を即座に確認できるインターフェースを提供します。

UI は「System（全体設定）」「Sound Effects（SE 調整）」「Background Music（BGM 調整）」の 3 つのブロックで構成されており、音量、ディレイ、リバーブ、エンベロープの聞き比べが可能です。

特に `tail_probe` という検証用 SE は、リバーブとエンベロープの差を明確に聞き分けるために設計されています。冒頭の短いアタック音で立ち上がりを確認し、その後の持続音でディケイやリバーブのテールを追跡できるため、音響調整の基準音として活用してください。

## サウンド機能の拡張とカスタマイズ

サウンド機能を拡張、または内部構造を変更する場合は、以下の関連箇所を確認してください。

- **ルーティングの変更:** `ToneSynth.ensureContext()`、`buildToneFxChain()`、`AudioSynth.buildBgmFxChain()`。
- **リバーブ特性の変更:** `getImpulseProfile()`、`writeImpulseTail()`、`addEarlyReflections()`。
- **BGM の挙動変更:** `startBgm()`、`scheduleBgm()`、`scheduleBgmStep()`。
- **プリセットの追加:** 共通のメロディや SE は `GameAudioSynth` へ追加し、アプリ固有のものはアプリ側の対応表で管理することを推奨します。
- **サンプルの UI 修正:** `webg/samples/sound/main.js` および関連ドキュメント。

特にインパルスレスポンスをカスタマイズする際は、「エフェクトの量（センド量）」と「空間の特性（IR）」の制御を混同させないことで、直感的な調整環境を維持できます。
