# `MobMedia.PCM`
[🔗](https://github.com/GenericJam/mob_media/blob/0.1.0/lib/mob_media/pcm.ex#L1)

Helpers for signed 16-bit little-endian interleaved PCM, the format
`MobMedia.audio_pcm/2` returns, plus the reference conversion the Android
decoder implements and `MobMedia.Engine.Fake` uses.

## Conversion

`convert/5` maps channels, then resamples each channel:

  * **Channels.** Same count: unchanged. To mono: the average of every
    channel (truncated). Mono to stereo: the sample on both sides. More
    than two channels to stereo: the first two (front left, front right).
  * **Downsampling** (`in_rate > out_rate`): output frame `k` is the average
    of input frames `floor(k * in / out)` up to, not including,
    `floor((k + 1) * in / out)`: a box filter, which is enough of a
    low-pass for speech recognition. A box is emitted once all of its input
    frames exist.
  * **Upsampling** (`in_rate < out_rate`): output frame `k` sits at input
    position `k * in / out` and is linearly interpolated between its two
    neighbours (the last input frame stands alone), rounded half up.
    Emitted while that position is inside the input.

So `n` input frames give about `n * out / in` output frames (within one).
iOS uses Core Audio's own converter instead, so its samples differ
slightly; the length is the same within a few milliseconds.

This is integer arithmetic only, so a streaming implementation (the Kotlin
bridge converts one decoder buffer at a time) produces exactly the same
samples as this one.

# `convert`

```elixir
@spec convert(binary(), pos_integer(), pos_integer(), pos_integer(), 1 | 2) ::
  binary()
```

Convert `pcm` from `in_rate` Hz / `in_channels` to `out_rate` Hz /
`out_channels` (1 or 2). See the moduledoc for the algorithm. A trailing
partial frame is dropped.

# `duration_ms`

```elixir
@spec duration_ms(binary(), pos_integer(), pos_integer()) :: non_neg_integer()
```

Length of `pcm` in milliseconds at `rate` Hz and `channels` channels (rounded down).

# `parse_wav`

```elixir
@spec parse_wav(binary()) ::
  {:ok, %{rate: pos_integer(), channels: pos_integer(), pcm: binary()}}
  | {:error, :unsupported}
```

Parse a WAV file holding 16-bit integer PCM (format 1, or
`WAVE_FORMAT_EXTENSIBLE` with the PCM subformat).
`{:error, :unsupported}` for anything else.

# `wav`

```elixir
@spec wav(binary(), pos_integer(), pos_integer()) :: binary()
```

A WAV file (RIFF, 16-bit PCM) holding `pcm` at `rate` Hz with `channels`
channels.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
