# `Cauldron2D.Audio`
[🔗](https://github.com/jaman/cauldron/blob/v0.2.2/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.
  * `keyed/4`, `moved/2` and `stopped/2` build events for sounds that keep sounding under
    a key: one replaces another under the same key, and each is placed again when the
    listener moves, when `moved/2` moves it, and when the effects level changes.
  * The stage sounds at most `:voices` sounds at once and lets the oldest go past that;
    a game sets it through `Cauldron2D.Client.Game` `audio/0`.
  * Effects and music have separate levels, set by `levels/3`; a `muted/1` event
    silences both until another lets them sound.
  * 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}`.

# `keyed`

```elixir
@spec keyed(term(), term(), Cauldron2D.Audio.Keyed.position(), keyword()) ::
  Cauldron2D.Audio.Keyed.t()
```

The event that sounds `name` under `key` at `at`. Whatever sounds under `key` is let go
first, over `:fade` seconds (default 0.05). The sound plays until it ends or `stopped/2`
lets it go; a run sounds as one, its voices' own pans left out. With `loop: true` a
sound that is a voice playing a recording goes round its recording — its own loop, or
the whole of it — until `stopped/2` lets it go; any other sound plays once.

`at` is one of:

  * `{x, y}` — placed against the listener, and placed again when the listener moves
  * `nil` — full level, centred
  * `{:level, gain, pan}` — the game's own level, `gain` 0.0 upward and `pan` from
    `-1.0` left to `1.0` right; the listener does not move it, `moved/2` does

The effects level scales every one of them, and the voice's own gain and pan apply on
top. Options: `:fade`, the seconds it takes to replace another, default 0.05; `:loop`,
`true` to sound round and round until stopped; `:music`, `true` for the sound to be
instead the recording `:recordings` gives for `name`, played in its own channels as a
looping layer of the stage's music (`TuningFork.Stage.layers/3`) from its own
beginning, at the music level and its placement's gain — `moved/2` changes that gain,
`stopped/2` drops the layer, and cueing a `Cauldron2D.Audio.Music` piece replaces these
layers. Raises `ArgumentError` for any other `at`.

    Audio.play(audio, [Audio.keyed({:entity, 12, :voice}, :announce, {40.0, 8.0})])
    Audio.play(audio, [Audio.keyed({:entity, 12, :weapon}, :spark, {:level, 0.4, -0.6})])

# `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.

# `moved`

```elixir
@spec moved(term(), Cauldron2D.Audio.Keyed.position()) :: Cauldron2D.Audio.Keyed.t()
```

The event that moves the sound under `key` to `at`, any `at` `keyed/4` takes. Nothing
happens when nothing sounds under `key`.

    Audio.play(audio, [Audio.moved({:entity, 12, :weapon}, {:level, 0.2, 0.9})])

# `mute`

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

Silence everything, or restore it.

# `muted`

```elixir
@spec muted(boolean()) :: {module(), :muted, boolean()}
```

The event that silences everything — effects and music — while `on?` is `true`, and
lets it sound again at the levels set when `false`. Sounds started meanwhile are kept
at no level, and come back with the rest.

# `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`
  * `:recordings` — a function from an event name to its recording,
    `%{rate: rate, channels: channels, pcm: pcm}` (signed 16-bit little-endian), or
    `nil`, for keyed music (`keyed/4`'s `music: true`). Default: none
  * `:master` — `:sfx` to have the effects level scale the music as well, the music
    sounding at `music × sfx`; `:none` for separate levels. Default `:none`
  * `: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

# `stopped`

```elixir
@spec stopped(term(), number()) :: Cauldron2D.Audio.Keyed.t()
```

The event that lets the sound under `key` go over `fade` seconds, default 0.05.

---

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