# 機能設計書 119-AudioListener

## 概要

本ドキュメントは、Three.jsにおける`AudioListener`クラスの機能設計を記述する。AudioListenerは、シーン内のすべての位置依存・非位置依存オーディオエフェクトの仮想リスナー（聴取者）を表現し、Web Audio APIのAudioListenerを抽象化したクラスである。

### 本機能の処理概要

AudioListenerクラスは、3Dシーン内でオーディオを聴く位置と向きを管理する。通常はカメラに追加され、カメラの3D変換がリスナーの3D変換として使用される。Three.jsアプリケーションでは通常1つのリスナーを作成し、AudioやPositionalAudioなどのオーディオエンティティに渡す。

**業務上の目的・背景**：3Dオーディオシステムにおいて、リスナー（聴取者）の位置と向きは音の空間的な知覚に不可欠である。AudioListenerは、Web Audio APIのAudioListenerをThree.jsのシーングラフと統合し、カメラ視点でのリアルな3Dオーディオ体験を実現する。

**機能の利用シーン**：
- 3Dゲームでのプレイヤー視点オーディオ
- VR/ARアプリケーションでの空間オーディオ
- 3Dシーンビューアでのオーディオ体験
- インタラクティブWebアプリケーションのBGM・効果音管理

**主要な処理内容**：
1. AudioContextの取得と管理
2. マスターボリューム制御（GainNode）
3. グローバルフィルターの設定
4. リスナー位置・向きの更新（updateMatrixWorld）

**関連システム・外部連携**：Audio、PositionalAudioのコンストラクタ引数として必須。Object3Dを継承しているため、カメラやシーングラフに追加可能。

**権限による制御**：特になし（ブラウザのオーディオ権限に依存）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | WebAudioサンプル | 主機能 | オーディオリスナーとして3Dオーディオを受信 |

## 機能種別

オーディオリスナー / 空間オーディオ / マスターボリューム制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| - | - | - | コンストラクタに引数なし | - |

### 入力データソース

- Object3D（親オブジェクトとしてカメラなど）のワールドマトリクス

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| context | AudioContext | Web Audioコンテキスト |
| gain | GainNode | マスターボリューム制御ノード |
| filter | AudioNode | グローバルフィルター（オプション） |
| timeDelta | number | linearRampToValueAtTime用のタイムデルタ |

### 出力先

- AudioContext.destination（スピーカー出力）

## 処理フロー

### 処理シーケンス

```
1. コンストラクタ
   └─ AudioContext.getContext()でcontext取得
   └─ createGain()でGainNode作成
   └─ gain.connect(context.destination)

2. setFilter() - グローバルフィルター設定
   └─ 既存フィルターがあれば切断
   └─ gain → filter → destination の順で接続

3. updateMatrixWorld() - リスナー位置更新
   └─ matrixWorldからposition/quaternion取得
   └─ forward/upベクトル計算
   └─ context.listenerに位置・向き設定
```

### フローチャート

```mermaid
flowchart TD
    A[AudioListener生成] --> B[AudioContext取得]
    B --> C[GainNode作成]
    C --> D[destinationに接続]
    D --> E{操作}
    E -->|setFilter| F{既存filter?}
    F -->|Yes| G[既存接続を切断]
    F -->|No| H[gainをdestinationから切断]
    G --> I[新filter接続]
    H --> I
    I --> J[gain→filter→destination]
    E -->|setMasterVolume| K[gain.gain.setTargetAtTime]
    E -->|updateMatrixWorld| L[matrixWorld分解]
    L --> M[position/quaternion取得]
    M --> N[forward/upベクトル計算]
    N --> O{positionX存在?}
    O -->|Yes| P[linearRampToValueAtTime]
    O -->|No| Q[setPosition/setOrientation]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-119-01 | シングルリスナー | アプリケーションに通常1つのリスナー | 設計指針 |
| BR-119-02 | カメラ子要素 | 通常カメラの子としてリスナーを追加 | 設計指針 |
| BR-119-03 | ブラウザ互換性 | Chrome用とレガシー用で異なるAPI使用 | updateMatrixWorld時 |

### 計算ロジック

**リスナー向きの計算**:
```javascript
_forward.set(0, 0, -1).applyQuaternion(_quaternion)
_up.set(0, 1, 0).applyQuaternion(_quaternion)
```

**位置更新タイミング**:
```javascript
endTime = context.currentTime + timeDelta
listener.positionX.linearRampToValueAtTime(position.x, endTime)
```

## データベース操作仕様

該当なし

## エラー処理

### エラーケース一覧

該当なし（Web Audio APIエラーはブラウザ依存）

### リトライ仕様

該当なし

## トランザクション仕様

該当なし

## パフォーマンス要件

- linearRampToValueAtTimeによるスムーズな位置補間
- timeDeltaを使用したフレーム間補間

## セキュリティ考慮事項

- ブラウザのオートプレイポリシーに準拠
- ユーザーインタラクション後にAudioContextを開始する必要がある場合あり

## 備考

- Object3Dを継承しているため、シーングラフに追加可能
- AudioやPositionalAudioの必須コンストラクタパラメータ
- Chrome向けの最適化（linearRampToValueAtTime使用）とレガシーブラウザ向けフォールバック

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: クラス構造を理解する

AudioListenerクラスの継承関係とプロパティを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AudioListener.js | `src/audio/AudioListener.js` | コンストラクタ（29-76行目） |

**読解のコツ**: 主要プロパティ
- `context`: AudioContext.getContext()で取得（41行目）
- `gain`: createGain()で作成、destinationに接続（49-50行目）
- `filter`: オプションのオーディオフィルター（61行目）
- `timeDelta`: フレーム間時間差（70行目）
- `_clock`: Clockインスタンス（74行目）

#### Step 2: 入出力メソッドを理解する

リスナーへの接続とフィルター設定を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AudioListener.js | `src/audio/AudioListener.js` | getInput()（85-89行目） |
| 2-2 | AudioListener.js | `src/audio/AudioListener.js` | setFilter()（128-147行目） |
| 2-3 | AudioListener.js | `src/audio/AudioListener.js` | removeFilter()（96-109行目） |

**主要処理フロー**:
- **85-89行目**: getInput() - gainを返す（Audioが接続に使用）
- **128-147行目**: setFilter() - フィルターを挿入（gain→filter→destination）
- **96-109行目**: removeFilter() - フィルターを削除し直接接続に戻す

#### Step 3: ボリューム制御を理解する

マスターボリュームの取得・設定を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AudioListener.js | `src/audio/AudioListener.js` | getMasterVolume()（154-158行目） |
| 3-2 | AudioListener.js | `src/audio/AudioListener.js` | setMasterVolume()（167-173行目） |

**主要処理フロー**:
- **154-158行目**: getMasterVolume() - gain.gain.valueを返す
- **167-173行目**: setMasterVolume() - setTargetAtTimeでスムーズに変更

#### Step 4: リスナー位置更新を理解する

updateMatrixWorldでのリスナー位置・向き更新を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AudioListener.js | `src/audio/AudioListener.js` | updateMatrixWorld()（175-212行目） |

**主要処理フロー**:
- **177行目**: super.updateMatrixWorld()呼び出し
- **181行目**: _clock.getDelta()でtimeDelta更新
- **183行目**: matrixWorldからposition/quaternion/scale分解
- **186-187行目**: forward(-Z)とup(Y)ベクトル計算
- **189-203行目**: Chrome向けlinearRampToValueAtTime
- **205-208行目**: レガシー向けsetPosition/setOrientation

### プログラム呼び出し階層図

```
AudioListener (extends Object3D)
    │
    ├─ constructor()
    │      ├─ AudioContext.getContext()
    │      ├─ context.createGain()
    │      └─ gain.connect(context.destination)
    │
    ├─ getInput()
    │      └─ return this.gain
    │
    ├─ setFilter(value)
    │      ├─ 既存filter切断
    │      ├─ this.filter = value
    │      ├─ gain.connect(filter)
    │      └─ filter.connect(destination)
    │
    ├─ removeFilter()
    │      ├─ gain.disconnect(filter)
    │      ├─ filter.disconnect(destination)
    │      ├─ gain.connect(destination)
    │      └─ this.filter = null
    │
    ├─ setMasterVolume(value)
    │      └─ gain.gain.setTargetAtTime(value, currentTime, 0.01)
    │
    └─ updateMatrixWorld(force)
           ├─ super.updateMatrixWorld(force)
           ├─ timeDelta = _clock.getDelta()
           ├─ matrixWorld.decompose(_position, _quaternion, _scale)
           ├─ _forward.set(0, 0, -1).applyQuaternion(_quaternion)
           ├─ _up.set(0, 1, 0).applyQuaternion(_quaternion)
           └─ listener.positionX.linearRampToValueAtTime() または
              listener.setPosition() / setOrientation()
```

### データフロー図

```
[入力]                    [処理]                         [出力]

カメラ.matrixWorld ─────▶ updateMatrixWorld() ─────────▶ context.listener
                                │                         position/orientation
                                ▼
                          matrixWorld分解
                                │
                                ▼
                          forward/upベクトル計算
                                │
                                ▼
オーディオグラフ:
Audio.gain ──────────────▶ AudioListener.gain ──────────▶ [filter] ──▶ destination
PositionalAudio.gain ────▶                                              (スピーカー)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AudioListener.js | `src/audio/AudioListener.js` | ソース | メインクラス定義 |
| AudioContext.js | `src/audio/AudioContext.js` | ソース | AudioContextシングルトン |
| Audio.js | `src/audio/Audio.js` | ソース | 非位置依存オーディオ（AudioListenerを使用） |
| PositionalAudio.js | `src/audio/PositionalAudio.js` | ソース | 位置依存オーディオ（AudioListenerを使用） |
| Object3D.js | `src/core/Object3D.js` | ソース | 基底クラス |
| Clock.js | `src/core/Clock.js` | ソース | タイムデルタ計算 |
| Vector3.js | `src/math/Vector3.js` | ソース | 位置・向きベクトル |
| Quaternion.js | `src/math/Quaternion.js` | ソース | 回転クォータニオン |
