# `TuningFork.Mixer`
[🔗](https://github.com/jaman/tuning_fork/blob/v0.1.11/tuning_fork/lib/tuning_fork/mixer.ex#L1)

Sums signed 16-bit little-endian PCM buffers, clipping at full scale, and pans mono sound.

# `bytes_per_frame`

```elixir
@spec bytes_per_frame(pos_integer()) :: pos_integer()
```

Bytes one frame occupies: two per channel.

# `centre`

```elixir
@spec centre(binary()) :: binary()
```

Copy a mono buffer to both channels, at the same constant-power level as `pan/3`.

# `clipped`

```elixir
@spec clipped(binary()) :: non_neg_integer()
```

How many samples sit at full scale.

# `fold`

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

Cut `pcm` to `frames` frames, mixing whatever ran past the end back over the start.

    iex> pcm = <<1, 0, 1, 0>> <> <<2, 0, 2, 0>>
    iex> TuningFork.Mixer.fold(pcm, 1, 2)
    <<3, 0, 3, 0>>

A buffer already `frames` long or shorter comes back as it is.

# `loudness`

```elixir
@spec loudness(binary()) :: float()
```

How loud a buffer is overall, as RMS in sample units. An empty buffer is 0.0.

# `mix`

```elixir
@spec mix([binary()]) :: binary()
```

Sum a list of buffers, passing over the silent ones. An empty list gives an empty buffer.

# `mix`

```elixir
@spec mix(binary(), binary()) :: binary()
```

Sum two buffers, sample by sample, clipping at full scale. The result is as long as the
longer buffer.

# `mix_at`

```elixir
@spec mix_at(binary(), binary(), non_neg_integer(), pos_integer()) ::
  {binary(), binary()}
```

Mix `buffer` into `base` starting `offset` frames in; a frame is one sample per `channels`.

Returns `{result, overflow}`. `result` is always exactly as long as `base`; `overflow` is
whatever ran off the end, ready to be mixed in somewhere else. An `offset` at or past the
end of `base` returns `base` unchanged with the whole of `buffer` as overflow.

# `normalise`

```elixir
@spec normalise(binary(), number(), keyword()) :: binary()
```

Scale a buffer so its `loudness/1` is `target`.

The scaling is limited so that the loudest sample lands no higher than `:ceiling` of full
scale, default 0.85, which means a very quiet buffer may come back below `target`. A silent
buffer is returned unchanged.

# `pan`

```elixir
@spec pan(binary(), float(), pos_integer()) :: binary()
```

Place a mono buffer in the stereo field, from `-1.0` hard left to `1.0` hard right.

`pan` outside that range is clamped to it. Panning is constant power, so a centred sound
sits about 3 dB below its mono render. A `channels` of 1 returns the buffer unchanged.

# `peak`

```elixir
@spec peak(binary()) :: non_neg_integer()
```

The largest absolute sample value in a buffer.

# `scale`

```elixir
@spec scale(binary(), float()) :: binary()
```

Scale a buffer's amplitude by `gain`, clipping at full scale.

A gain of 1.0 returns the buffer unchanged; a gain at or below 0.0 gives silence of the
same length.

# `silence`

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

A buffer of silence, `frames` frames long. A negative `frames` gives an empty buffer.

# `silent?`

```elixir
@spec silent?(binary()) :: boolean()
```

Whether every sample of `pcm` is zero.

# `soft_clip`

```elixir
@spec soft_clip(binary(), float()) :: binary()
```

Round off a buffer's peaks instead of flattening them.

Samples quieter than `threshold` of full scale pass through untouched; louder ones are bent
smoothly towards full scale and never reach it, so `clipped/1` on the result is zero.
`threshold` runs 0.0 to 1.0 and defaults to 0.7; at 1.0 the buffer is returned unchanged.

    iex> loud = for _ <- 1..4, into: <<>>, do: <<32_767::16-signed-little>>
    iex> TuningFork.Mixer.clipped(TuningFork.Mixer.soft_clip(loud))
    0

# `take`

```elixir
@spec take(binary(), pos_integer(), pos_integer()) :: {binary(), binary()}
```

Take `frames` frames from the front of a buffer.

Returns `{chunk, rest}`. `chunk` is always exactly `frames` frames, padded with silence if
the buffer is shorter, in which case `rest` is empty.

# `to_mono`

```elixir
@spec to_mono(binary()) :: binary()
```

Fold a stereo buffer down to mono by averaging each frame's two samples.

---

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