# `TuningFork.Mixer`
[🔗](https://github.com/jaman/tuning_fork/blob/v0.1.12/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.

# `fit`

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

`pcm`, signed 16-bit little-endian at `from_rate` with `from_channels` channels, as
`to_rate` with `to_channels`: one channel doubled to two, two folded to one
(`to_mono/1`), the first two kept of more; a different rate read between frames by
straight lines. The same rate and channels give `pcm` back as it is.

# `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.

# `place`

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

Place a mono buffer in the field while its gain and pan move from `from` to `to`, each a
`{gain, pan}` pair with `pan` as in `pan/3`.

Each channel's constant-power level moves in a straight line from the first frame to the
last, so a placement changed between buffers has no step in it. With `channels` 1 only the
gain moves. Equal `from` and `to` place the buffer as `pan/3` and `scale/2` would.

    Mixer.place(mono, {1.0, 0.0}, {0.5, -1.0}, 2)

# `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*
