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

Notes on a beat grid with a tempo map, rendered to one buffer that loops without a break.

    Score.from_parts([bass, arpeggio, drums], beats: 64)
    |> Score.render(44_100)

# `layer`

```elixir
@type layer() :: %{fx: keyword(), notes: [{float(), TuningFork.Voice.t()}]}
```

# `t`

```elixir
@type t() :: %TuningFork.Score{
  beats: float(),
  bpm: float(),
  changes: [{float(), float()}],
  layers: [layer()],
  notes: [{float(), TuningFork.Voice.t()}]
}
```

# `add`

```elixir
@spec add(t(), number(), TuningFork.Voice.t()) :: t()
```

Place a voice on a beat.

# `add_all`

```elixir
@spec add_all(t(), [number()], TuningFork.Voice.t()) :: t()
```

Place the same voice on each of `beats`.

# `beat_to_seconds`

```elixir
@spec beat_to_seconds(t(), number()) :: float()
```

Where `beat` falls, in seconds, read through the tempo map.

# `duration`

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

How long the score runs, in seconds: where `:beats` falls on the tempo map.

# `from_parts`

```elixir
@spec from_parts([TuningFork.Part.t()], keyword()) :: t()
```

Build a score by mixing parts together.

Each part is read from its own beat zero. Every part must have the same `:bpm`; parts that
disagree raise `ArgumentError`.

## Options

  * `:bpm` — the tempo, default the parts'. All parts play at it
  * `:beats` — how long the score is, default the longest part's `TuningFork.Part.beats/1`
  * `:changes` — tempo changes, as `new/1` takes them

# `new`

```elixir
@spec new(keyword()) :: t()
```

An empty score.

## Options

  * `:bpm` — the tempo it starts at, default 120
  * `:beats` — how long it runs, default 16
  * `:changes` — tempo changes after the start, as `[{beat, bpm}]`. Sorted by beat, and
    where two name the same beat the later one in the list wins

# `note`

```elixir
@spec note(atom() | number()) :: float()
```

The frequency of a named note, such as `:a3`, `:fs4` or `:eb5`.

# `note`

```elixir
@spec note(atom() | number(), integer()) :: float()
```

The frequency `interval` semitones from `name`.

# `render`

```elixir
@spec render(t(), pos_integer(), keyword()) :: binary()
```

Render to signed 16-bit little-endian PCM that loops without a break.

`rate` is samples per second. The result is `duration/1` seconds long, and at least one
frame; anything running past the end is folded back over the start. Each voice lands where
its `:pan` says.

## Options

  * `:channels` — 2 for stereo, the default, or 1 for mono

# `repeat`

```elixir
@spec repeat(t(), number(), number(), TuningFork.Voice.t()) :: t()
```

Place a voice every `every` beats, from `from` up to but not including `:beats`.

Raises `ArgumentError` unless `every` is greater than zero.

# `seconds_to_beat`

```elixir
@spec seconds_to_beat(t(), number()) :: float()
```

Which beat `seconds` falls on, read through the tempo map. The inverse of `beat_to_seconds/2`.

# `tempo`

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

Change tempo at `beat`, for everything from there until the next change.

A change at a beat that already has one replaces it. A score runs at one tempo at a time.

# `tempo_map`

```elixir
@spec tempo_map(t()) :: [{float(), float()}]
```

Every tempo this score runs at, as `[{beat, bpm}]`, starting from beat zero.

A change stated at beat zero is the starting tempo and replaces `:bpm`.

---

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