mcelp 1.0.1

Mitsubishi CELP speech codec: a 3.6 kbit/s speech encoder and decoder
Documentation
# 実装の検証方法

このコーデックは演算そのもので規定されています。丸め方の違うデコーダは同じ
ビットから異なる出力を生みます。したがって「それらしく聴こえる」は有用な検証に
なりません。テストスイートが確認するのは、この実装が参照実装を**ビット単位で**
再現することであり、それを3つの層で行います。

```sh
cargo test --release
```

テストファイル125個、データファイル117個、参照値4.1 MB。

Cargo はこれらのソースを `tests/reference_suite.rs` から1つの統合テストターゲット
として取り込みます。モジュール名とテスト名の粒度は保ったまま、ライブラリのリンクを
ソースごとではなく1回だけにしています。最上位へ追加したテストソースが未登録なら、
ハーネス自身のテストが失敗します。

## 第1層 — 段ごとのテスト

スイートの大半は1段につき1ファイルで、参照実装から採取した値を単一の関数に
流します。

```
tests/levinson.rs       ← src/lpc.rs        levinson()
tests/chebyshev.rs      ← src/lsp.rs        根の探索
tests/shortlist ...     ← src/pulses.rs     探索の各ブロック
```

それぞれ `tests/data/` に同名のデータファイルがあり、1行が1呼び出しです。入力が
先、続いて参照実装が生成した値。列の意味はテストの doc コメントに書かれています。

この粒度こそが要点です。エンコーダの状態は前方に流れるため、ある段の値の誤りは
1フレームの異常としては現れず、数フレーム後の発散として現れます。段ごとのテストは
その段の演算が食い違う**最初の呼び出し**で落ちるので、故障を単一の関数に局在化
できます。

## 第2層 — 連鎖テスト

いくつかのテストは2〜3段を合成し、段の間で正しい形の値が受け渡されているかを
確認します。名前は `*_chain` です。

| テスト | 連鎖 |
|---|---|
| `quantise_chain` | 余弦領域 → 周波数 → 重み → 量子化器 → 伝送フィールド |
| `adaptive_gain_chain` | 相互相関 → 適応利得 |
| `gain_search_chain` | 5つの測度 → 指数の整列 → 利得索引 |
| `lpc_chain`, `codebook_chain`, `shaping_chain` | 他の箇所での同じ考え方 |

連鎖テストは段ごとのテストでは捕まえられない種類のバグを捕まえます。2つの関数が
それぞれ単体では正しいのに、境界でスケール係数や指数の規約が食い違っている場合
です。

## 第3層 — 端から端まで

| テスト | 固定するもの |
|---|---|
| `encode_endtoend` | 同梱2例に対するエンコーダ出力、フレーム単位 |
| `endtoend` | 同梱2例に対するデコーダ出力、バイト単位。加えて補償経路 |
| `api` | 公開 API を通したバイト単位の往復 |

`examples/` には対応の取れた一式が入っています——`*.orig.ulaw`(入力)、
`*.mcelp`(参照エンコーダのビットストリーム)、`*.decoded.ulaw`(参照デコーダの
出力)——ので、双方向が同じ素材に固定されます。どこか1ビットでも動く変更はこれらで
落ちます。

## 何が検証できていないか

明示しておく価値があります。

- **例が2つだけ。** 男声・女声それぞれ75フレーム。この2録音がたまたま通る経路は
  よく覆われていますが、どちらも通らない経路は、そこに届く段ごとのテストがある
  場合にのみ覆われます。
- **データで引く表。** どちらの例も選ばないコードブックエントリは一度も読まれ
  ません。表そのものは完全ですが、その隅は未検証です。
- **消失補償は自己整合のみ。** 例には消失フレームが含まれないため、`endtoend`  抑圧ビットを立てて人工的に作ります。これは補償経路が動作し自身と歩調を保つ
  ことを確認するもので、参照実装の補償と突き合わせてはいません。

## コードに手を入れるとき

以上から従う事柄をいくつか。

**スイート全体を release で走らせる。** debug ビルドは20倍ほど遅く、スイートは
大量のデータを再生します。コミット前には両プロファイルを走らせる価値があります。
debug にはオーバーフロー検査と `debug_assert` があり、release にはありません。

**演算を「修正」しない。** `acc` は実機が巻き戻す場所の印であり、取り繕われた
バグではなく仕様の一部です。飽和に置き換えたり、累算の順序を変えたりすると出力が
変わります。[fixed-point.md](fixed-point.md) を参照してください。

**カバレッジを足すなら段ごとのテストを優先する。** 食い違いを見つけたら、それが
現れる最小の関数の入出力を採取する方が、端から端までのケースを1つ増やすより
価値があります。

**表の境界に注意する。** いくつかの表は記憶域を共有していたり、隣の表からの
オフセットで読まれたりします。現在は境界検査が入っているので範囲外読み出しは
隣の表のデータを黙って返すのではなくパニックしますが、表のサイズを変える際は、
公称の長さを前提にせず、それを添字する側のコードを確認してください。