# `Cauldron2D.Audio.Music`
[🔗](https://github.com/jaman/cauldron/blob/v0.1.3/cauldron_2d/lib/cauldron_2d/audio/music.ex#L1)

Music in sections and layers, switched on bar boundaries and mixed per listener.

A game declares a piece — its tempo, its layer names, and for each section what every
layer plays — and then cues it from its own state. The names are the game's; nothing
here knows what a section or a layer is for.

    Cauldron2D.Audio.Music.piece(audio, "orbit", %{
      bpm: 112,
      beats_per_bar: 4,
      layers: [:drums, :bass, :pad],
      sections: %{
        calm: %{drums: calm_drums, bass: calm_bass, pad: pad},
        tense: %{drums: tense_drums, bass: tense_bass, pad: pad}
      },
      fingerprint: "3"
    })

    Cauldron2D.Audio.Music.cue(audio, %{piece: "orbit", section: :tense, layers: %{drums: 1.0, bass: 1.0, pad: 0.4}})

What a layer plays is either performed live or rendered once:

  * a Strudel chain as a string — `~s|s("bd*4").bank("RolandTR909")|` — is performed
    live on the listener's stage. A section's chains play as one `stack`, each layer's
    level applied to its chain as its `velocity` — multiplying whatever `gain` the
    chain sets, never replacing it — and the music level to the whole, sounding notes
    included, at the piece's `bpm / beats_per_bar / 60` cycles per second. Nothing is rendered ahead, so a cue takes effect at once:
    a new section at the next cycle line, new gains on the next block. Every layer of
    a section must be a chain for the section to play this way
  * a `TuningFork.Score`, a zero-arity function returning PCM, or PCM as `{:pcm, binary}` is rendered once,
    two stems at a time in tasks, and kept through `TuningFork.Cache` in the game's
    music directory under
    `piece/section/layer` and a fingerprint of the piece's own, what was written, the
    tempo, the rate and channels — so a changed layer is rendered again. A cue moves its section to the front of
    what is still to render and takes effect when the section is ready — at the next
    bar boundary of the playing section, with a cross-fade; new gains at once

The music level from `Cauldron2D.Audio.levels/3` scales either.

A director made static (`Cauldron2D.Audio` started with `static_music: true`) plays
the first section it is cued and no other: the section's chains are rendered once,
as one pattern, over the piece's `:loop` cycles (default 8) — whatever still sounds at
the end folded back over the start, so it loops without a seam — kept through
`TuningFork.Cache` under `piece/section/static` and the fingerprint, and looped as a
bed. Every later cue is ignored until `stop/1`. Rendering costs once a section and
rate on a node; playing it back costs a copy a chunk.

# `cue`

```elixir
@type cue() :: %{
  :piece =&gt; String.t(),
  :section =&gt; atom(),
  optional(:layers) =&gt; %{required(atom()) =&gt; number()}
}
```

# `renderable`

```elixir
@type renderable() ::
  String.t() | TuningFork.Score.t() | (-&gt; binary()) | {:pcm, binary()}
```

# `spec`

```elixir
@type spec() :: %{
  :bpm =&gt; number(),
  :beats_per_bar =&gt; pos_integer(),
  :layers =&gt; [atom()],
  :sections =&gt; %{required(atom()) =&gt; %{required(atom()) =&gt; renderable()}},
  optional(:fingerprint) =&gt; String.t()
}
```

# `t`

```elixir
@type t() :: %{
  pieces: map(),
  rendered: map(),
  wanted: cue() | nil,
  playing: {String.t(), atom()} | nil
}
```

# `cue`

```elixir
@spec cue(Cauldron2D.Audio.instance(), cue()) :: :ok
```

Ask for a section of a piece with the given layer gains; see the module rules.

# `current`

```elixir
@spec current(Cauldron2D.Audio.instance()) :: {String.t(), atom()} | nil
```

The piece and section playing, as `{piece, section}`, or `nil`.

# `piece`

```elixir
@spec piece(Cauldron2D.Audio.instance(), String.t(), spec()) :: :ok
```

Declare a piece and start rendering every section of it.

# `prefetch`

```elixir
@spec prefetch([{String.t(), spec()}]) :: :ok
```

Fetch every recording and soundfont `pieces` play, in the background, so that their
first bars are not synthesised while the files arrive. Walks the first 64
cycles of every chain; a chain that does not read is skipped. Returns at once.

# `render_static`

```elixir
@spec render_static(String.t(), spec(), atom(), pos_integer(), 1 | 2, keyword()) ::
  binary()
```

The section of `spec` a static director loops for `piece`, as PCM at `rate` and
`channels`: rendered now and kept if it was not before, read back if it was, in
`:dir` (default `TuningFork.Cache.dir/0`; a game's is
`Cauldron2D.Client.Game.music_dir/1`). A server that will play static music calls
this for its pieces as it starts, so the first listener does not wait for the
rendering.

# `stop`

```elixir
@spec stop(Cauldron2D.Audio.instance()) :: :ok
```

Stop the music.

---

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