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

A process that mixes sounding voices, loops, patterns and scores into one stream and
writes it to a `TuningFork.Sink`.

    {:ok, _pid} = TuningFork.Stage.start_link(sink: TuningFork.Sink.Speaker)
    TuningFork.Stage.play(TuningFork.Voice.new(freq: 440.0))

# `t`

```elixir
@type t() :: GenServer.server()
```

# `after_round`

```elixir
@spec after_round(t(), term(), timeout()) :: :ok | {:error, :no_such_loop | :timeout}
```

Block until loop `name` next comes round.

Returns `:ok` when it does, `{:error, :no_such_loop}` at once when nothing is running under
that name, or `{:error, :timeout}` after `timeout` milliseconds. A loop that is not looping
never comes round.

# `beat`

```elixir
@spec beat(t()) :: float() | nil
```

Which beat the transport is on, or `nil` if no score is playing.

# `bed`

```elixir
@spec bed(t(), binary()) :: :ok
```

Loop `pcm` underneath everything else, replacing any bed already playing.

`pcm` must be at the stage's rate and channel count. It is read by sample position and
wraps where the buffer ends, however short it is. An empty binary clears the bed.

# `bed_gain`

```elixir
@spec bed_gain(t(), float()) :: :ok
```

Set how loud the bed is, from 0.0 to 1.0, from the next chunk. Values outside that range
are clamped.

# `bed_position`

```elixir
@spec bed_position(t()) :: non_neg_integer() | nil
```

The playhead of the layer set, in frames since it started, or `nil` with no set.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `clear_bed`

```elixir
@spec clear_bed(t()) :: :ok
```

Stop the bed. Anything sounding over it keeps playing.

# `cpm_of`

```elixir
@spec cpm_of(number()) :: float()
```

Cycles per minute from cycles per second.

# `cps_of`

```elixir
@spec cps_of(number()) :: float()
```

Cycles per second from cycles per minute.

    iex> TuningFork.Stage.cps_of(120)
    2.0

# `cycle`

```elixir
@spec cycle(t()) :: float() | nil
```

Where the pattern has reached, in cycles, or `nil` when none is playing.

Whole numbers are cycle lines, so `3.75` is three quarters of the way through cycle three.

# `hold`

```elixir
@spec hold(t(), term(), TuningFork.Voice.t()) :: :ok
```

Sound `voice` and keep it sounding under `key` until `release/3`, however long its
envelope's hold is. A voice already held under `key` is released first. Ignored while
muted.

    Stage.hold(stage, {1, 60}, Kit.voice(%{note: 60, s: "gm_piano"}, 1.0))
    Stage.release(stage, {1, 60})

# `hush`

```elixir
@spec hush(t()) :: :ok
```

Silence everything the pattern has already started, without stopping the pattern.

# `layer_gains`

```elixir
@spec layer_gains(t(), %{required(term()) =&gt; number()}) :: :ok
```

Set the gain of the named layers, 0.0 to 1.0, from the next chunk.

# `layers`

```elixir
@spec layers(t(), %{required(term()) =&gt; binary()}, keyword()) :: :ok
```

Loop a set of named layers underneath everything else, all read from one playhead.

`layers` maps a name to PCM at the stage's rate and channel count; each wraps at its
own length. The set replaces the one playing unless `keep: true`, which merges into it.
`bed/2` is the same as a set with one layer named `:bed`.

## Options

  * `:gains` — a map from name to 0.0..1.0, default 1.0 for every layer
  * `:keep` — merge into the playing set instead of replacing it. Default `false`
  * `:at` — `{:bar, frames}` delays the change until the playhead next reaches a
    multiple of `frames`. Default: at once
  * `:fade_ms` — cross-fade from the old set to the new over this many milliseconds.
    Default `0`

# `level`

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

The peak sample of the last chunk written to the sink, after effects and limiting, 0.0 to
1.0. `0.0` before anything has been written.

# `loops`

```elixir
@spec loops(t()) :: %{
  required(term()) =&gt; %{
    beat: float(),
    rounds: non_neg_integer(),
    pending?: boolean()
  }
}
```

Every loop running, as `%{name => %{beat: beat, rounds: rounds, pending?: boolean}}`.

`rounds` is how many times that loop has been round; `pending?` is whether a swapped score
is waiting for the next round.

# `mute`

```elixir
@spec mute(t(), boolean()) :: :ok
```

Set whether `play/2` and `play_pcm/2` are ignored. Muting does not silence what is already
sounding or affect the bed.

# `pattern_cpm`

```elixir
@spec pattern_cpm(t(), number()) :: :ok
```

Run the pattern at `cpm` cycles per minute, keeping its place.

# `pattern_cps`

```elixir
@spec pattern_cps(t(), number()) :: :ok
```

Run the pattern at a different speed, in cycles per second, keeping its place.

# `pattern_gain`

```elixir
@spec pattern_gain(t(), number()) :: :ok
```

Scale the pattern's whole output by `gain`, sounding notes included, from the next chunk; kept for patterns started later.

# `play`

```elixir
@spec play(t(), TuningFork.Voice.t()) :: :ok
```

Sound a voice. Returns immediately; the voice is rendered in the stage. Ignored while muted.

# `play_pcm`

```elixir
@spec play_pcm(t(), binary()) :: :ok
```

Sound already-rendered PCM.

It must be at the stage's rate and channel count. Ignored while muted.

# `release`

```elixir
@spec release(t(), term(), number()) :: :ok
```

Let the voice held under `key` go, fading over `seconds` (default 0.05) from where it is.
Nothing happens when no voice is held under `key`.

# `scope`

```elixir
@spec scope(t(), pos_integer()) :: [float()]
```

The last `:scope` frames of the left channel as written to the sink, aligned to a rising
zero crossing and thinned to `points` samples from -1.0 to 1.0. `[]` before anything has
been written.

# `sounding`

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

How many voices are sounding right now, the transport's included.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Start a stage, linked to the caller.

A sink that will not open is logged and replaced by `TuningFork.Sink.Silent`; the stage
still keeps time.

## Options

  * `:name` — the registered name, default `TuningFork.Stage`
  * `:rate` — samples per second, default 44100
  * `:chunk` — frames per write, default 512
  * `:channels` — 2 for stereo, the default, or 1 for mono. Passed to the sink as well
  * `:sink` — a `TuningFork.Sink`, default `TuningFork.Sink.configured/0`
  * `:sink_opts` — passed to the sink's `open/1`, with `:rate` and `:channels` filled in
  * `:voices` — most voices sounding at once, default 16. Past that the oldest are dropped
  * `:fx` — effects over everything the stage produces, as `[reverb: [...]]`, taking what
    `TuningFork.Fx.Live.new/3` takes
  * `:limit` — the threshold `TuningFork.Mixer.soft_clip/2` rounds peaks off above, 0.0
    to 1.0, default 0.7; `nil` for none
  * `:scope` — frames of recent audio kept for `scope/2`, default 4096

# `start_loop`

Start a named loop, playing `score` round and round from its own beat zero.

    Stage.start_loop(stage, :bass, Score.from_parts([bass]))

Starting a loop under a name already running replaces it from the next chunk. `opts` are
`TuningFork.Transport.new/3`'s, with `:loop` defaulting to true, and one more:

  * `:body` — a zero-arity function returning `{:ok, score}` or `{:error, reason}`, run
    once for each round to give the next round's score. It is run in its own process
    during the round before; a body that raises, returns an error or does not finish in
    time leaves the loop playing what it played last

# `start_loop`

# `start_loop`

```elixir
@spec start_loop(t(), term(), TuningFork.Score.t(), keyword()) :: :ok
```

# `start_pattern`

Play a `TuningFork.Pattern` from cycle zero until `stop_pattern/1`, replacing any pattern
already playing.

`opts` are `TuningFork.Pattern.Player.new/3`'s: `:cps`, `:voice`, `:voices` and
`:parallel`. The
pattern is synthesised in a process of its own (`TuningFork.Pattern.Ahead`), a few
chunks ahead of the stream, so a change to it — `update_pattern/3`, `pattern_gain/2`,
`pattern_cps/2`, `hush/1` — sounds within those chunks, and `cycle/1` reports the
place the synthesis has reached.

# `start_pattern`

# `start_pattern`

```elixir
@spec start_pattern(t(), TuningFork.Pattern.t(), keyword()) :: :ok
```

# `start_score`

```elixir
@spec start_score(t(), TuningFork.Score.t(), keyword()) :: :ok
```

Play a score in time, note by note, replacing any score already playing.

`opts` are `TuningFork.Transport.new/3`'s: `:loop` and `:playing`. A transport that reaches
the end without looping is dropped.

# `stop_all`

```elixir
@spec stop_all(t()) :: :ok
```

Silence everything currently sounding. The bed and any transport keep going.

# `stop_loop`

```elixir
@spec stop_loop(t(), term()) :: :ok
```

Stop one loop. What it has already started is left to finish sounding.

# `stop_loops`

```elixir
@spec stop_loops(t()) :: :ok
```

Stop every loop at once.

# `stop_pattern`

```elixir
@spec stop_pattern(t()) :: :ok
```

Stop the pattern. Anything it has already started is left to finish sounding.

# `stop_score`

```elixir
@spec stop_score(t()) :: :ok
```

Stop the transport. Anything it has already started is left to finish sounding.

# `update_loop`

Swap a loop's score without moving where it has got to.

`at: :round`, the default, holds the new score until the loop comes round; `at: :now` takes
effect on the next chunk. `:body` replaces the loop's body. Ignored for a name that is not
running.

# `update_loop`

# `update_loop`

```elixir
@spec update_loop(t(), term(), TuningFork.Score.t(), keyword()) :: :ok
```

# `update_pattern`

Swap the playing pattern without moving the position.

`opts` are `TuningFork.Pattern.Player.update/3`'s: `at: :cycle`, the default, holds it until
the next cycle line; `at: :now` takes effect on the next chunk. Ignored when no pattern is
playing.

# `update_pattern`

# `update_pattern`

```elixir
@spec update_pattern(t(), TuningFork.Pattern.t(), keyword()) :: :ok
```

# `update_score`

```elixir
@spec update_score(t(), TuningFork.Score.t()) :: :ok
```

Swap the playing score without moving the position.

Heard from the next chunk. Notes already sounding finish as they were. Ignored when no
score is playing.

---

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