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/1orstart_link/1comes 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?/1reports 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:earshottiles — and panned by its side; an unplaced one plays at full level, centred. keyed/4,moved/2andstopped/2build 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, whenmoved/2moves it, and when the effects level changes.- The stage sounds at most
:voicessounds at once and lets the oldest go past that; a game sets it throughCauldron2D.Client.Gameaudio/0. - Effects and music have separate levels, set by
levels/3; amuted/1event 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/1andresume_bed/1cover both playback modes.- Music with sections and layers is
Cauldron2D.Audio.Music.
Summary
Functions
Loop the PCM render produces under key; see the module rules for caching.
Returns a specification to start this module under a supervisor.
Stop the music, live or rendered.
The effects and music levels an instance starts with: %{sfx: 0.85, music: 0.35}.
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.
Set the effects and music levels, each 0.0 to 1.0.
Where the listener is, for placed events.
The event that moves the sound under key to at, any at keyed/4 takes. Nothing
happens when nothing sounds under key.
Silence everything, or restore it.
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.
Whether a stage with somewhere to play is running.
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 score on a loop, live or from a render, under key.
Sound voice directly, scaled by the effects level.
The key last given to bed/4 or play_piece/4, or nil.
Start the music last asked for again.
The stage this instance plays through, or nil.
Start the default instance, registered as Cauldron2D.Audio, replacing one already
running. Returns whether sound is possible.
Start an instance linked to the caller.
The event that lets the sound under key go over fade seconds, default 0.05.
Types
@type instance() :: GenServer.server()
@type sound() :: TuningFork.Voice.t() | [{non_neg_integer(), TuningFork.Voice.t()}] | nil
Functions
Loop the PCM render produces under key; see the module rules for caching.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear_bed(instance()) :: :ok
Stop the music, live or rendered.
The effects and music levels an instance starts with: %{sfx: 0.85, music: 0.35}.
@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 movesnil— full level, centred{:level, gain, pan}— the game's own level,gain0.0 upward andpanfrom-1.0left to1.0right; the listener does not move it,moved/2does
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})])
Set the effects and music levels, each 0.0 to 1.0.
Where the listener is, for placed events.
@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})])
Silence everything, or restore it.
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.
Whether a stage with somewhere to play is running.
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.
@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—:liveor:rendered, overriding the instance's default:render— a zero-arity function returning the PCM to loop in:renderedmode. Default: rendersscoreat the stage's rate:fingerprint— keys the on-disk render together withkey. Default"0"
@spec play_voice(instance(), TuningFork.Voice.t()) :: :ok
Sound voice directly, scaled by the effects level.
The key last given to bed/4 or play_piece/4, or nil.
@spec resume_bed(instance()) :: :ok
Start the music last asked for again.
The stage this instance plays through, or nil.
Start the default instance, registered as Cauldron2D.Audio, replacing one already
running. Returns whether sound is possible.
Takes the options of start_link/1.
@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, ornilfor none. DefaultCauldron2D.Audio:sounds— a function from an event name to asound/0. Default: silent:sink— aTuningFork.Sinkmodule, or{module, sink_opts}. Default:TuningFork.Sink.Speakerwhen that package is present, else nothing plays:stage— an already running stage to use instead of starting one:mode— the default forplay_piece/4,:liveor:rendered. Default:live:sfx,:music— levels 0.0 to 1.0. Defaultdefault_levels/0:recordings— a function from an event name to its recording,%{rate: rate, channels: channels, pcm: pcm}(signed 16-bit little-endian), ornil, for keyed music (keyed/4'smusic: true). Default: none:master—:sfxto have the effects level scale the music as well, the music sounding atmusic × sfx;:nonefor separate levels. Default:none:earshot— tiles at which a placed event falls silent. Default30.0:music_dir— the directory rendered music is kept in; a game's isCauldron2D.Client.Game.music_dir/1. DefaultTuningFork.Cache.dir/0:static_music— play the first section cued and nothing after, rendered once and looped; seeCauldron2D.Audio.Music. Defaultfalse: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. Defaultfalse:rate,:channels,:chunk,:voices,:lead,:fx— the stage's settings. Default44_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
@spec stopped(term(), number()) :: Cauldron2D.Audio.Keyed.t()
The event that lets the sound under key go over fade seconds, default 0.05.