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

Sound effects and music for one listener, over one `TuningFork` stage.

A game emits the events that happened — `:brick`, `{:explosion, {x, y}}` — and supplies
a function from an event's name to the voice it makes. Each instance is a process; a
game with one player uses the default one, a served game starts one per session with a
sink that reaches that player.

    Cauldron2D.Audio.start(sounds: &MyGame.Sound.voice/1, sfx: 0.85, music: 0.35)
    Cauldron2D.Audio.play([:brick, {:wall, {12.0, 3.0}}])
    Cauldron2D.Audio.bed(Cauldron2D.Audio, "mygame/level-3", fn -> MyGame.Music.render(3) end)

    {:ok, audio} = Cauldron2D.Audio.start_link(name: nil, sink: {TuningFork.Sink.Pulse, server: "tcp:127.0.0.1:<port>"})
    Cauldron2D.Audio.listener(audio, {player_x, player_y})
    Cauldron2D.Audio.play(audio, events)

Every function takes the instance first. The ones with a fixed number of arguments
default it to the process registered as `Cauldron2D.Audio`; `play_piece/4` and `bed/4`
take it explicitly.

## Rules

  * `start/1` or `start_link/1` comes first. It records the sound table and opens the
    stage; when this machine has no sound the stage stays silent and every call is a
    no-op returning `:ok`. `on?/1` reports which.
  * An event is any term the table knows, or `{event, {x, y}}` placing it. With a listener set, a placed event is
    scaled by its distance — full within one tile, silent at `:earshot` tiles — and
    panned by its side; an unplaced one plays at full level, centred.
  * Effects and music have separate levels, set by `levels/3`.
  * Beds are keyed by a string. A render runs once, in a task, and is kept in memory and
    on disk via `TuningFork.Cache`; a bed finishing after another key was asked for is
    dropped. A render that raises is logged and never plays.
  * `clear_bed/1` and `resume_bed/1` cover both playback modes.
  * Music with sections and layers is `Cauldron2D.Audio.Music`.

# `event`

```elixir
@type event() :: term() | {term(), {number(), number()}}
```

# `instance`

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

# `sound`

```elixir
@type sound() ::
  TuningFork.Voice.t() | [{non_neg_integer(), TuningFork.Voice.t()}] | nil
```

# `bed`

```elixir
@spec bed(instance(), String.t(), (-&gt; binary()), keyword()) :: :ok
```

Loop the PCM `render` produces under `key`; see the module rules for caching.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `clear_bed`

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

Stop the music, live or rendered.

# `default_levels`

```elixir
@spec default_levels() :: %{sfx: float(), music: float()}
```

The effects and music levels an instance starts with: `%{sfx: 0.85, music: 0.35}`.

# `levels`

```elixir
@spec levels(instance(), number(), number()) :: :ok
```

Set the effects and music levels, each 0.0 to 1.0.

# `listener`

```elixir
@spec listener(instance(), {number(), number()} | nil) :: :ok
```

Where the listener is, for placed events.

# `mute`

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

Silence everything, or restore it.

# `on?`

```elixir
@spec on?(instance()) :: boolean()
```

Whether a stage with somewhere to play is running.

# `play`

```elixir
@spec play(instance(), [event()]) :: :ok
```

Sound `events` in order through the table given at start: a name — any term — or `{name, {x, y}}` placed against the listener; one the table answers `nil` for, or has no clause for, is silent.

# `play_piece`

```elixir
@spec play_piece(instance(), String.t(), TuningFork.Score.t(), keyword()) :: :ok
```

Play `score` on a loop, live or from a render, under `key`.

## Options

  * `:mode` — `:live` or `:rendered`, overriding the instance's default
  * `:render` — a zero-arity function returning the PCM to loop in `:rendered` mode.
    Default: renders `score` at the stage's rate
  * `:fingerprint` — keys the on-disk render together with `key`. Default `"0"`

# `play_voice`

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

Sound `voice` directly, scaled by the effects level.

# `playing`

```elixir
@spec playing(instance()) :: String.t() | nil
```

The key last given to `bed/4` or `play_piece/4`, or `nil`.

# `resume_bed`

```elixir
@spec resume_bed(instance()) :: :ok
```

Start the music last asked for again.

# `stage`

```elixir
@spec stage(instance()) :: pid() | nil
```

The stage this instance plays through, or `nil`.

# `start`

```elixir
@spec start(keyword()) :: boolean()
```

Start the default instance, registered as `Cauldron2D.Audio`, replacing one already
running. Returns whether sound is possible.

Takes the options of `start_link/1`.

# `start_link`

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

Start an instance linked to the caller.

The instance lives as long as the caller: when the caller exits, for any reason, the
instance stops, and a stage it started stops with it, closing its sink. A stage handed
in with `:stage` is left running.

## Options

  * `:name` — a registered name, or `nil` for none. Default `Cauldron2D.Audio`
  * `:sounds` — a function from an event name to a `t:sound/0`. Default: silent
  * `:sink` — a `TuningFork.Sink` module, or `{module, sink_opts}`. Default:
    `TuningFork.Sink.Speaker` when that package is present, else nothing plays
  * `:stage` — an already running stage to use instead of starting one
  * `:mode` — the default for `play_piece/4`, `:live` or `:rendered`. Default `:live`
  * `:sfx`, `:music` — levels 0.0 to 1.0. Default `default_levels/0`
  * `:earshot` — tiles at which a placed event falls silent. Default `30.0`
  * `:music_dir` — the directory rendered music is kept in; a game's is
    `Cauldron2D.Client.Game.music_dir/1`. Default `TuningFork.Cache.dir/0`
  * `:static_music` — play the first section cued and nothing after, rendered once
    and looped; see `Cauldron2D.Audio.Music`. Default `false`
  * `:parallel` — synthesise the music's voices on every core, for one player on a
    machine of their own (the terminal, the desktop); a server with many players
    leaves it off. Default `false`
  * `:rate`, `:channels`, `:chunk`, `:voices`, `:lead`, `:fx` — the stage's settings.
    Default `44_100`, `2`, `256`, `12`, `2048`, `[]`: the sink is kept 2048 frames
    (46 ms at 44.1 kHz) ahead of playback, in a ring of four times that, so a few
    milliseconds of scheduling jitter never leave it empty

---

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