TuningFork.Mixer (TuningFork v0.1.11)

Copy Markdown View Source

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

Summary

Functions

Bytes one frame occupies: two per channel.

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

How many samples sit at full scale.

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

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

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

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

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

Scale a buffer so its loudness/1 is target.

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

The largest absolute sample value in a buffer.

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

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

Whether every sample of pcm is zero.

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

Take frames frames from the front of a buffer.

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

Functions

bytes_per_frame(channels)

@spec bytes_per_frame(pos_integer()) :: pos_integer()

Bytes one frame occupies: two per channel.

centre(mono)

@spec centre(binary()) :: binary()

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

clipped(pcm)

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

How many samples sit at full scale.

fold(pcm, frames, channels \\ 2)

@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(pcm)

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

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

mix(buffers)

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

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

mix(a, b)

@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(base, buffer, offset, channels \\ 2)

@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(pcm, target, opts \\ [])

@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(mono, pan, arg3)

@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(pcm)

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

The largest absolute sample value in a buffer.

scale(pcm, gain)

@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(frames, channels \\ 2)

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

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

silent?(pcm)

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

Whether every sample of pcm is zero.

soft_clip(pcm, threshold \\ 0.7)

@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(buffer, frames, channels \\ 2)

@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(stereo)

@spec to_mono(binary()) :: binary()

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