# ラウドネス

`createLoudness` は、マイクの音量をゲームから読める 0〜3 のレベルに変えます。音声はアナライザーまでしか届きません。Earshot は音声を録音、送信、再生しません。

## マイク入力 {#mic-input}

```ts
import { createLoudness } from "@debuffjp/earshot";

const loud = createLoudness(ctx);
const off = loud.onLevel((level) => { if (level >= 2) monster.hear(player); });

// ボタンから呼びます。部屋の音を測る 3 秒間、プレイヤーには静かにしてもらいます。
const { ok, error } = await loud.enableMic();

loud.level; // 0〜3
loud.micStatus; // "off" | "calibrating" | "on"
loud.disableMic();
off();
```

`createLoudness` には `AudioContext` が必要です。同じタップやキー入力で resume してください。`enableMic()` は reject しません。マイクを拒否されると `{ ok: false, error }` が返り、`error` はブラウザのエラー名です。`press` はそのまま使えます。

ブラウザは要求を無視することがあるので、実際の設定は `loud.applied` で確認してください。エコーキャンセル、ノイズ抑制、自動ゲインは音量の差を平らにするため、デフォルトでオフです。要求するなら `browserProcessing: true` を渡します。

マイクには HTTPS か localhost が必要です。iframe には `allow="microphone"` も必要です。iPhone ではマイクを開くとゲームの音に影響する場合があります。[制限](./#limits)を見てください。

## キャリブレーション {#calibration}

`enableMic()` はマイクを開き、`calibrateMs`（デフォルトは 3000）の間音を聞いてから完了します。その読み取り値の 20 パーセンタイルがノイズフロアになります。事前にプレイヤーへ静かにするよう伝え、`micStatus` が `"calibrating"` の間はそれを表示してください。

部屋の状況が変わったら、測り直します。

```ts
await loud.recalibrate(); // 先にプレイヤーへ静かにするよう伝えます。
loud.overFloorDb; // ノイズフロアから何 dB 上か。調整済みのマイクがなければ null
```

## レベル {#levels}

レベルはノイズフロアを基準にするので、部屋の騒がしさによらず同じように動きます。

| レベル | ノイズフロアからの差 |
| --- | --- |
| 0 | 10 dB 未満 |
| 1 | 10 dB 以上 |
| 2 | 20 dB 以上 |
| 3 | 30 dB 以上 |

| オプション | 意味 | デフォルト |
| --- | --- | --- |
| `steps` | レベル 1、2、3 のノイズフロアからの dB | `[10, 20, 30]` |
| `attackMs` | レベルが上がるまでに大きな音が続くべき時間 | `150` |
| `releaseMs` | レベルが下がるまでに静かな状態が続くべき時間 | `400` |
| `hysteresisDb` | 音がしきい値をこの dB だけ下回るまで、レベルを保ちます | `5` |

`loud.level` はマイクと `press` の大きいほうです。`onLevel` は値が変わったときだけ呼ばれ、コールバックを外す関数を返します。

自分の読み取り値を渡すなら、同じ処理をマイクなしで使える `createLevelDetector` があります。

```ts
import { createLevelDetector } from "@debuffjp/earshot";

const detector = createLevelDetector({ steps: [6, 12, 18] });
detector.calibrate([-62, -60, -61, -60]); // 静かな間に取った dB の値
detector.update(-40, 16); // dB と、前回の読み取りからのミリ秒
```

`calibrate` を呼ぶまで、`update` は 0 を返します。

## heardDb {#hearddb}

壁と距離をゲームのルールに反映するには、モンスターのいる場所での音量を求めます。しきい値はゲームに合わせて調整してください。私たちのデモでは 6 dB がちょうどよく感じられました。

```ts
import { computeMix, heardDb, wallsOnGrid, DEFAULT_MATERIALS, DEFAULT_PARAMS } from "@debuffjp/earshot";

const walls = wallsOnGrid(tiles, player, monster); // どちらもタイル単位。
const mix = computeMix(player, monster, walls, DEFAULT_PARAMS, DEFAULT_MATERIALS);
if (loud.level >= 2 && heardDb(loud.level, mix) >= 6) monster.wake();
```

`heardDb(level, mix)` は、レベルの音量にミックスのゲインを dB で加えます。レベル 0 と範囲外のミックスは、どちらも `-Infinity` になります。`steps` を変えたときは、レベル 0 を先頭にした同じ値を第 3 引数に渡してください。

```ts
heardDb(loud.level, mix, [-Infinity, 6, 12, 18]);
```

## アクセシビリティのルール {#accessibility-rules}

- `press()` を使うキーかボタンを必ず用意してください。
- 言葉や長時間の発声を必須にしないでください。
- マイクの用途をプレイヤーに伝えてから許可を求めてください。

`press` は次に変えるまでレベルを保ちます。マイクでできることは、すべてマイクなしでもできます。

```ts
addEventListener("keydown", (e) => { if (e.code === "KeyF") loud.press(2); });
addEventListener("keyup", (e) => { if (e.code === "KeyF") loud.press(0); });
```