mcelp 1.0.1

Mitsubishi CELP speech codec: a 3.6 kbit/s speech encoder and decoder
Documentation
# 分析合成 — エンコーダのサブフレームループ

「分析合成」という言葉は、具体的でやや込み入った機構を覆い隠しています。この
文書では、その1サブフレーム分を、エンコーダが実際に踏む順序で追います。

## 何が問題か

*合成した出力*が入力に最も近くなる励振を選びたい。素直な定式化——全候補を合成
して比較——は費用が高すぎます。2つの観察がこれを扱える形にします。

1. 合成フィルタは**線形**である。候補がスケールされたパルスの和なら、その出力も
   同じスケールのパルス応答の和になる。したがってフィルタはインパルスに対して
   1回だけ適用すればよく、各候補は事前計算されたベクトルの組合せで採点できる。
2. フィルタの**記憶**はどの候補についても同じで、既に符号化済みのサブフレームから
   来る。出力のその部分は1回計算して入力から引いておけばよく、残るのは選ぼうと
   している励振だけに依存する*目標信号*になる。

どちらも定石ですが、2つめが以下の目標信号の計算がやっていることです。

## 1サブフレームの手順

### 1. 残差

そのサブフレームの整形信号の窓を `A(z)`——合成フィルタの逆——に通し、LPC 残差を
得ます。

```
残差 = A(z) · 整形信号
```

### 2. 残差を励振バッファに置く

他の何よりも先に、その残差が励振バッファの現サブフレーム位置に書き込まれます。
このサブフレームの励振はまだ選ばれていないので早すぎるように見えますが、意図的
です。80標本より短いピッチラグは、このサブフレームがまだ埋めていない領域に届き
ます。残差で種を蒔いておくのが参照実装の振る舞いで、その後、適応コードブックの
周期的延長が上書きします。

### 3. 誤差記憶を初期値とした再合成

```
再合成 = 1/A(z) · 残差        誤差記憶 (error_memory) から開始
```

`error_memory` は、入力とデコーダが再構成するものとの差分の直近10標本を保持
します。フィルタを零状態ではなくこの記憶から始めるのが仕掛けです。出てくるのは、
入力信号から「既に符号化済みのサブフレームが生成する分」を差し引いたものです。

### 4. 重み付け

```
重み付き = A(z/γ₁) · [誤差記憶 ‖ 再合成]
目標     = 1/A(z/γ₂) · 重み付き        残差記憶 (residual_memory) から開始
```

結果が**目標信号**です。重み付けされた信号から、前のサブフレームが既に説明して
いる分をすべて取り除いたもの。3つの探索はすべてこれに対して合わせにいきます。

### 5. インパルス応答

探索は重み付き合成の連鎖全体のインパルス応答を必要とします。

```
δ → A(z/γ₁) → 1/A(z) → 1/A(z/γ₂) → h[0…79]
```

零状態から、サブフレームごとに1回、80標本分。候補の励振はフィルタリングではなく
`h` との畳み込みで評価され、これがコードブック探索を実行可能にしています。

### 6. 3つの探索、その順序

```
閉ループのピッチラグ探索      → 適応寄与
モード判定                    → このサブフレームの符号化方法
固定コードブック探索          → 励振波形
利得探索                      → 両利得を同時に
```

順序は依存関係で決まります。残りに対して固定コードブックを探索するには適応寄与が
先に必要であり、両者が決まらなければ利得は選べず、モード判定はサブフレームの
周期性を測るために適応寄与を必要とします。

各探索は同じ `相関² / 電力` の比で採点され、交差乗算で比較されるので、ループの
どこにも除算はありません。

### 7. 励振の組み立てと記憶の更新

2つの索引と利得索引が決まったら、デコーダが組み立てるのと寸分違わぬ手順で励振を
組み立て、以下に通します。

- 合成フィルタ(`speech_memory` を更新);
- 重み付けの分母(`residual_memory` を更新);
- 入力と再構成の差分(`error_memory` を更新)。

## エンコーダがデコーダを模倣する理由

手順7が、手順6で量子化したばかりの値を*逆*量子化し、デコーダのフィルタに通して
いることに注目してください。エンコーダは自分が計算した非量子化値を一切使わず、
デコーダが見ることになる値を使います。

これは防御的な実装ではなく、正しさの要件です。次のサブフレームの目標信号はこれら
の記憶から導かれるので、エンコーダがデコーダの再構成ではなく自分の理想信号を
追跡していたら、両者は乖離し、以降のすべてのサブフレームがデコーダの居ない状態に
対して最適化されてしまいます。

同じ規律はフレーム層にも現れます。線スペクトル量子化器は*復号後*の出力を予測器
記憶に戻し、補間の判定は復号後フィルタの反射係数に対して行われます——デコーダが
同じ値に同じ検査を行い、伝えられなくても同じ結論に至るように。

## デバッグへの帰結

状態が前方に流れるため、エンコーダのバグが1フレームだけの異常として現れることは
稀です。わずかに狂ったフレームが出て、その後**発散**していきます。以降のすべての
サブフレームが誤った記憶に対して最適化されるからです。

テストスイートが各段を端から端までではなく個別に参照データと突き合わせるのは
そのためです。段ごとのテストはその段の演算が食い違う最初の呼び出しで落ちますが、
端から端までのテストは「どこかで何かがずれた」としか教えてくれません。
[testing.md](testing.md) を参照してください。