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

Recorded audio, to be played as a `TuningFork.Voice`.

    kick = Sample.load!("kick.wav")
    part(bpm: 96, synth: Voice.new(sample: kick)) |> steps("x..x..x.")

# `t`

```elixir
@type t() :: %TuningFork.Sample{
  id: term(),
  loop: {non_neg_integer(), pos_integer()} | nil,
  name: String.t(),
  pcm: binary(),
  rate: pos_integer(),
  root: float() | nil
}
```

# `at`

```elixir
@spec at(t(), float()) :: float()
```

The value at a fractional frame position in the recording, from -1.0 to 1.0.

Cubic Hermite interpolation through the two frames either side and their neighbours. A
position before the start or past the end is 0.0 rather than a wrap, unless the recording
has a loop, which `looped/2` brings it back inside.

# `duration`

```elixir
@spec duration(t()) :: float()
```

How long the recording lasts at its own speed, in seconds.

# `duration`

```elixir
@spec duration(t(), float() | nil) :: float()
```

How long the recording lasts when played at `freq`, in seconds.

Longer when pitched down and shorter when pitched up. A sample with no `:root`, or a `freq`
of `nil`, gives `duration/1`.

# `frames`

```elixir
@spec frames(t()) :: non_neg_integer()
```

How many frames long the recording is.

# `from_pcm`

```elixir
@spec from_pcm(binary(), keyword()) :: t()
```

A sample from PCM already in memory.

Stereo input is folded to mono, since a voice is synthesised in mono and panned afterwards.
More than two channels raises `ArgumentError`.

## Options

  * `:rate` — samples per second of the PCM given, default 44100
  * `:channels` — 1 or 2, default 1
  * `:root` — the pitch the recording already is, as a note name or a frequency in Hz
  * `:loop` — `{from, to}` frames a voice goes round between once it reaches `to`, so a
    note can outlast the recording; default `nil`
  * `:gain` — a factor the recording is scaled by as it is loaded, default 1.0
  * `:name` — what to call it, default `"sample"`

# `load!`

```elixir
@spec load!(Path.t(), keyword()) :: t()
```

Read a recording from disk: a WAV or a FLAC as they are, anything else through
`TuningFork.Sample.Decode`.

Which it is comes from the file's own header, not its name. Raises if the file cannot be read
or will not decode.

## Options

  * `:root` — the pitch the recording already is, as a note name or a frequency in Hz.
    Leave it out for percussion, which has no pitch to move from
  * `:name` — what to call it, default the file's basename

`:rate` and `:channels` come from the file and are not taken here.

# `looped`

```elixir
@spec looped(t(), float()) :: float()
```

`position` brought back inside the loop when the recording has one and the position has
passed its end; unchanged otherwise.

# `ratio`

```elixir
@spec ratio(t(), float() | nil, pos_integer()) :: float()
```

How far to move through the recording per output frame to sound at `freq` through a device
running at `rate`.

Two ratios in one: `freq` against the sample's `:root`, and the sample's own rate against
`rate`. A sample with no `:root`, or a `freq` of `nil`, is read at its own pitch and still
corrected for rate.

# `reverse`

```elixir
@spec reverse(t()) :: t()
```

A new sample with the recording reversed, keeping its rate and root.

# `slice`

```elixir
@spec slice(t(), number(), number()) :: t()
```

A new sample cut out of this one: `length` seconds starting `from` seconds in.

A range running past the end is truncated to what is there. The result keeps the rate and
root of the original and gets its own `:id`, so caches tell it apart from what it came
from.

---

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