mcelp 1.0.1

Mitsubishi CELP speech codec: a 3.6 kbit/s speech encoder and decoder
Documentation
# アーキテクチャ

## 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コアで符号化・復号した
場合:

| | 音声3秒あたり | 実時間比 |
|---|---:|---:|
| 符号化 | 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` だけです。各段の
モジュールが公開されているのは、それぞれを単体で参照データと突き合わせられる
ようにするためで、テストスイートがまさにそれを行っています。