# アーキテクチャ
## 3つの時間単位
コーデック全体が、入れ子になった3つの長さを軸に構成されています。
```
フレーム 320 標本、40 ms 伝送パケット1個
半フレーム 160 標本 線スペクトル1組、分析窓1枚
サブフレーム 80 標本 励振探索1回
```
なぜ2段でなく3段なのか。線スペクトルは送るのが高価なのでフレームごとに1組だけ
送り、**補間**して各半フレーム・各サブフレームのフィルタを作ります。励振は作る
のは安いが信号に密に追従する必要があるので、フレームあたり4回探索します。
半フレームはその中間の単位で、エンコーダのスペクトル分析と雑音抑圧が160標本
ずつ動きます。
## エンコーダ
```
μ-law 入力 (320)
│
├─ preprocess ──────── 線形へ伸張、高域通過
│
├─ analysis ────────── 256標本窓、128点FFT、振幅スペクトル
│ │
│ ├─ bands ──────── 129ビンを20臨界帯域に畳み込み、dB へ変換
│ ├─ noise ──────── 2本の長期雑音床、無音判定フレームでのみ更新
│ ├─ vad ────────── 帯域統計から音声区間スコアを算出
│ └─ weights ────── 帯域ごとの抑圧深度
│
├─ shaping ─────────── 抑圧を適用、逆変換、スパンを縫い合わせ
│ ↓ 以降すべてが見るのはこの整形済み信号
│
├─ lpc ─────────────── 400標本の自己相関、Levinson-Durbin、
│ 線スペクトル周波数へ変換
├─ lsf_weight ──────── 量子化 ───────────────────────► フィールド 0, 1
│
└─ サブフレームごと (×4):
weighting ─────── 聴覚重み付けフィルタを構成
pitch_search ──── 開ループでラグ、閉ループで精密化 ─► ラグフィールド
mode ──────────── このサブフレームの符号化方法を決定
pulses ────────── 固定コードブック探索 ───────────► 符号フィールド
gain ──────────── 利得コードブック探索 ───────────► 利得フィールド
excitation ────── 励振を組み立て、フィルタ記憶を更新
```
サブフレームループの詳細は [encoder-loop.md](encoder-loop.md)、その土台に
なっている重み付けフィルタは [weighting.md](weighting.md) で扱います。
エンコーダは進みながら合成します。励振を選んだ後、デコーダが使うのと同じ合成
フィルタに通し、次のサブフレームの探索がデコーダと寸分違わぬ状態から始まるように
します。これが「分析合成」の実際であり、エンコーダのバグが単発の異常フレーム
ではなく**ドリフト**として現れる理由でもあります。
## デコーダ
```
18 バイト入力
│
├─ bitstream ───────── 14フィールドを取り出す
├─ lsp ─────────────── 線スペクトルを逆量子化、サブフレームごとに補間
│
├─ サブフレームごと (×4):
│ pitch ─────────── 適応コードブック:過去の励振をラグ位置で再標本化
│ codebook ──────── 固定コードブック:索引から励振波形を再構成
│ gain ─────────── 両利得を逆量子化、符号利得を予測
│ decoder ───────── 2つの寄与を加算して励振とする
│ synth ─────────── 1/A(z) を通して音声を得る
│
├─ postfilter ──────── サブフレームごとに短期・長期ポストフィルタ、
│ 傾き補償、利得整合
└─ output filter ───── 最終再構成フィルタ、μ-law へ圧伸
```
## 時間はどこに使われるか
CELP で高価なものはすべて探索側にあり、実測もそれを裏づけます。
`examples/male.orig.ulaw` の3秒を、デスクトップ CPU の1コアで符号化・復号した
場合:
| 符号化 | 37 ms | 81倍 |
| 復号 | 2.5 ms | 1184倍 |
エンコーダはデコーダの約**15倍**の計算量です。この非対称性はこの実装の都合では
なく設計に内在します。デコーダは1つの励振を評価するだけですが、エンコーダは
サブフレームごとに数百の候補を評価し、さらに勝者を合成もするからです。
エンコーダ内部では固定コードブック探索が支配的です。候補集合が最大で、索引に
最も多くのビットを費やす段でもあります。次に大きいのがスペクトル前段で、
フレームあたり2回、FFT と20帯域の統計を回します。
どちらも実時間を大きく上回るので、現代の計算機では実用上の制約になりません。
最適化がどこで効くかを考える際に意味を持つのは、絶対値ではなくこの比です。
## フレームを跨いで残る状態
コーデックの両側とも状態を持ちます。これがストリームを途中から復号できない
理由です。デコーダが保持するもの:
| 線スペクトル予測器の記憶 | 量子化器が予測型で、過去3フレームが次に効く |
| 前半フレームの線スペクトル | 補間の一方の端点 |
| 合成フィルタ記憶 | `1/A(z)` の10標本 |
| 励振履歴 | 154標本。最大143のラグが届くように |
| 合成音声・残差の履歴 | ポストフィルタの入力 |
| 利得予測器の記憶 | 符号利得電力の過去4フレーム分 |
| ポストフィルタ記憶、AGC 状態 | サブフレーム間の連続性 |
| 補償用ラグ、乱数状態 | 消失フレーム用 |
エンコーダはデコーダの状態一式(歩調を合わせるため必須)に加えて、自前の状態
——雑音床、音声区間検出のカウンタ、抑圧スケール、分析窓の履歴、開ループの
ピーク履歴——を持ちます。
## モジュール対応表
| `bitstream` | フレームの詰め込みと取り出し |
| `preprocess`, `ulaw` | 圧伸と入力整形 |
| `analysis`, `bands`, `noise`, `vad`, `frontend`, `weights`, `shaping` | エンコーダ前段 |
| `lpc`, `lsf_weight` | 予測分析と線スペクトル量子化 |
| `lsp` | 線スペクトルの表現、逆量子化、補間 |
| `weighting`, `weight_lpc` | 聴覚重み付け |
| `pitch_search`, `convolve` | ピッチ探索 |
| `pitch` | 適応コードブック |
| `pulses`, `shape_pair`, `mode` | 固定コードブック探索 |
| `codebook` | 固定コードブックの復号 |
| `gain` | 利得コードブック(双方向) |
| `excitation` | サブフレーム励振の組み立て |
| `synth` | 合成フィルタ |
| `postfilter`, `ltp` | デコーダのポストフィルタ |
| `encoder`, `decoder` | 各段を束ねるフレームループ |
| `fixed` | 演算プリミティブ |
| `tables` | 定数表 |
外部から呼ぶことを想定しているのは `Encoder` と `Decoder` だけです。各段の
モジュールが公開されているのは、それぞれを単体で参照データと突き合わせられる
ようにするためで、テストスイートがまさにそれを行っています。