# audio

`import "github.com/matjam/bunyip/audio"`

Package audio mixes sounds in Go and passes float32 stereo frames to the platform's output device. A Sound holds decoded samples at the mixer's rate. To play one, call Play, which returns a Voice that can be adjusted or stopped while it runs. Music streams from a decoder instead of being held in memory. Voices can be placed in the listener's world (with distance, panning or a binaural head model, Doppler and occlusion), filtered, sent to a reverb, faded, pitched, muted, soloed and prioritised, and grouped on a Bus to be turned down, muted or paused together. The mixer has one shared reverb, which a ReverbZone replaces while the listener is inside that zone, and a Bus can carry a reverb of its own. Mixing happens on the audio device's thread, so every method is safe to call from the game loop, and every change in gain ramps across a block so nothing clicks; stopping a voice ramps it to silence over about a millisecond first. A setter copies its value in under a short lock and the mixer applies it at the start of the next block, so setters do not wait for a whole block. While the mixer waits for that lock, setters yield to it, so a game calling setters in a tight loop does not hold a block back. Stream.Read runs without the settings lock but with the playback lock held; it may call setters or start voices, but must not call Voice.Seek. Voice.Seek waits for the block in flight, because it moves the playhead being read.

The decoders (Decode, DecodeWAV, DecodeOGG, DecodeMP3, DecodeFLAC) take the whole file as bytes. OpenMusic borrows a seekable reader; OpenMusicFile owns a file and releases it after Music.Close joins decoding.

OpenCapture records from a selected input into a ring the game drains with Read. It is separate from the mix: what it records is not played back unless the game plays it. Record retains bounded PCM; RecordPCM and RecordWAV stream to borrowed writers, and RecordWAVFile owns its output file. Stop joins capture and output processing. PCM and Sound expose WriteWAV and SaveWAV for export.

Playback advances only when an output device pulls mixed blocks. Headless runs, Config.NoAudio and unavailable output devices leave playheads stationary; do not use audio completion to drive essential game progress in those modes.

## Variables

<a id="ErrCaptureDropped"></a>

```go
var ErrCaptureDropped = errors.New("audio: capture dropped samples")
```

ErrCaptureDropped reports lost input samples. Recording stops rather than returning a silently incomplete recording as a success.

<a id="ErrDeviceUnavailable"></a>

```go
var ErrDeviceUnavailable = audioout.ErrUnavailable
```

ErrDeviceUnavailable means the requested endpoint could not be opened.

<a id="ErrDeviceUnsupported"></a>

```go
var ErrDeviceUnsupported = audioout.ErrUnsupported
```

ErrDeviceUnsupported means this operating system or installation has no backend.

<a id="ErrNoDevice"></a>

```go
var ErrNoDevice = errors.New("audio: this run has no audio device")
```

ErrNoDevice is returned by OpenCapture when the engine explicitly disabled devices for a headless run or Config.NoAudio. Failure to open an output device alone does not disable an independent capture.

<a id="ErrNotSeekable"></a>

```go
var ErrNotSeekable = errors.New("audio: stream cannot seek")
```

ErrNotSeekable is returned by Voice.Seek for a stream that cannot seek.

## Types

<a id="Attenuation"></a>

<a id="Attenuation.Model"></a>

<a id="Attenuation.Rolloff"></a>

### Attenuation

```go
type Attenuation struct {
	Model   AttenuationModel
	Rolloff float32
}
```

Attenuation controls distance gain. Rolloff is nonnegative; zero means 1. All models except None are full volume within MinDistance and silent at MaxDistance. The zero value preserves the engine's original curve.

<a id="AttenuationModel"></a>

### AttenuationModel

```go
type AttenuationModel uint8
```

AttenuationModel selects distance gain between a voice's distance limits.

<a id="AttenuationDefault"></a>

<a id="AttenuationNone"></a>

<a id="AttenuationLinear"></a>

<a id="AttenuationInverse"></a>

<a id="AttenuationExponential"></a>

```go
const (
	AttenuationDefault     AttenuationModel = iota // inverse distance times linear cutoff (legacy)
	AttenuationNone                                // distance does not affect gain
	AttenuationLinear                              // linear falloff
	AttenuationInverse                             // inverse-distance falloff
	AttenuationExponential                         // power-law falloff
)
```

<a id="Bus"></a>

### Bus

```go
type Bus struct {
	// contains filtered or unexported fields
}
```

Bus groups voices so they can be turned up, down or paused together. Every voice plays through one bus, or straight through the master when PlayOptions.Bus is nil; its gain is master × bus × voice. A mixer starts with three buses, Music, Effects and Dialogue, and games make more with NewBus. Bus methods are safe to call from the game loop.

Example:

```go
package main

import (
	"fmt"

	"github.com/matjam/bunyip/audio"
)

func main() {
	// Voices play through a bus so a settings screen can turn music down
	// without touching effects, and a pause menu can hold them all.
	m := audio.NewMixer(48000)
	m.Music().SetVolume(0.4)
	footsteps := m.NewBus("footsteps") // buses beyond the three built in
	step, _ := m.NewSound(audio.Sine(120, 0.05, 48000))
	m.Play(step, audio.PlayOptions{Bus: footsteps})
	footsteps.SetVolume(0.6)
	m.SetPaused(true) // the pause menu: everything holds, nothing is lost
	fmt.Println(m.Playing(), footsteps.Volume())
}
```

Output:

```
1 0.6
```

<a id="Bus.Muted"></a>

#### Bus.Muted

```go
func (b *Bus) Muted() bool
```

Muted reports whether the bus is muted.

<a id="Bus.Name"></a>

#### Bus.Name

```go
func (b *Bus) Name() string
```

Name is the name the bus was made with.

<a id="Bus.Paused"></a>

#### Bus.Paused

```go
func (b *Bus) Paused() bool
```

Paused reports whether the bus is paused.

<a id="Bus.SetMute"></a>

#### Bus.SetMute

```go
func (b *Bus) SetMute(mute bool)
```

SetMute silences every voice on the bus while they keep playing, so unmuting resumes wherever the sound has reached. The gain ramps over one block, so it never clicks.

<a id="Bus.SetPaused"></a>

#### Bus.SetPaused

```go
func (b *Bus) SetPaused(p bool)
```

SetPaused holds every voice on the bus in place, silent, until resumed; voices started while the bus is paused wait too. The pause fades out over the block it lands in, so it never clicks.

<a id="Bus.SetReverb"></a>

#### Bus.SetReverb

```go
func (b *Bus) SetReverb(s ReverbSettings)
```

SetReverb gives the bus a reverb of its own. Voices on the bus send to it instead of the mixer's shared reverb, so a cave's effects can ring while the music stays dry, or the reverse. The zero value removes it. The settings reach the reverb at the start of the next mixed block.

<a id="Bus.SetSolo"></a>

#### Bus.SetSolo

```go
func (b *Bus) SetSolo(solo bool)
```

SetSolo solos the bus. While any bus is soloed, every other bus, and every voice on no bus, is silent and keeps playing. Clearing the last solo makes the rest audible again.

<a id="Bus.SetVolume"></a>

#### Bus.SetVolume

```go
func (b *Bus) SetVolume(v float32)
```

SetVolume scales every voice on the bus; 1 is unity. The change ramps across the next mixed block, so it never clicks.

<a id="Bus.Soloed"></a>

#### Bus.Soloed

```go
func (b *Bus) Soloed() bool
```

Soloed reports whether the bus is soloed.

<a id="Bus.Volume"></a>

#### Bus.Volume

```go
func (b *Bus) Volume() float32
```

Volume returns the bus gain.

<a id="Capture"></a>

### Capture

```go
type Capture struct {
	// contains filtered or unexported fields
}
```

Capture is a running recording from the input selected by CaptureOptions. An empty DeviceID uses the machine's default input. The device fills a ring buffer from its own thread and the game drains it with Read, which does not wait for samples to arrive. It briefly shares a mutex with the device's buffer writer. Samples that are not read in time are dropped, oldest first, so a game that stops reading falls behind by at most the buffer.

A Capture is not a Stream: to play what is recorded, feed the samples to a Stream of the game's own, or to a Sound built from them.

<a id="Capture.Buffered"></a>

#### Capture.Buffered

```go
func (c *Capture) Buffered() int
```

Buffered is how many recorded samples are waiting to be read.

<a id="Capture.Channels"></a>

#### Capture.Channels

```go
func (c *Capture) Channels() int
```

Channels is how many channels each frame has; 1 is mono.

<a id="Capture.Close"></a>

#### Capture.Close

```go
func (c *Capture) Close()
```

Close stops the stream and releases the device. Samples already buffered can still be read. Closing twice, or from two goroutines, does the work once; the second call waits for the first.

<a id="Capture.Dropped"></a>

#### Capture.Dropped

```go
func (c *Capture) Dropped() int64
```

Dropped counts the samples the device recorded and overwrote because the game did not read them in time. A rising count means either a longer CaptureOptions.Buffer or more frequent reads.

<a id="Capture.Level"></a>

#### Capture.Level

```go
func (c *Capture) Level() float32
```

Level is the root mean square of the samples the device delivered most recently, from 0 to 1, which is what a level meter draws. It moves at the device's own block rate, a few hundred times a second, and holds its last value when the stream is closed.

<a id="Capture.Rate"></a>

#### Capture.Rate

```go
func (c *Capture) Rate() int
```

Rate is the sample rate the stream records at.

<a id="Capture.Read"></a>

#### Capture.Read

```go
func (c *Capture) Read(dst []float32) int
```

Read copies at most len(dst) recorded samples into dst and reports how many it copied, which is 0 when nothing has arrived since the last call. It does not wait for new samples, but may briefly wait for the buffer mutex. With more than one channel the samples are interleaved, so a caller that works in frames reads a multiple of Channels.

<a id="CaptureOptions"></a>

<a id="CaptureOptions.DeviceID"></a>

<a id="CaptureOptions.Rate"></a>

<a id="CaptureOptions.Channels"></a>

<a id="CaptureOptions.Buffer"></a>

### CaptureOptions

```go
type CaptureOptions struct {
	DeviceID string  // empty selects the system default input; see InputDevices
	Rate     int     // samples per second; 0 is the mixer's rate
	Channels int     // 1..32; 0 is 1, mono
	Buffer   float32 // finite positive seconds held before dropping oldest samples; 0 is 0.5
}
```

CaptureOptions shape a capture stream. Zero values mean the defaults noted.

<a id="Cone"></a>

<a id="Cone.InnerAngle"></a>

<a id="Cone.OuterAngle"></a>

<a id="Cone.OuterGain"></a>

### Cone

```go
type Cone struct{ InnerAngle, OuterAngle, OuterGain float32 }
```

Cone directs a source's sound. Angles are full aperture angles in radians, 0 \<= InnerAngle \<= OuterAngle \<= 2\*pi. Gain interpolates from 1 inside the inner cone to OuterGain (0..1) outside the outer cone. The zero value is omnidirectional.

<a id="DeviceInfo"></a>

<a id="DeviceInfo.ID"></a>

<a id="DeviceInfo.Name"></a>

<a id="DeviceInfo.Default"></a>

### DeviceInfo

```go
type DeviceInfo struct {
	ID      string // pass to SetOutputDevice or CaptureOptions.DeviceID
	Name    string // display name; not necessarily unique
	Default bool   // the default endpoint when enumerated
}
```

DeviceInfo identifies an output or input endpoint. IDs are opaque OS local selection identifiers, not list indices or user identities. They can become unavailable after device, driver or configuration changes. Linux endpoints include configured PCMs.

<a id="Listener"></a>

<a id="Listener.Position"></a>

<a id="Listener.Forward"></a>

<a id="Listener.Up"></a>

<a id="Listener.Velocity"></a>

### Listener

```go
type Listener struct {
	Position lin.Vec3 // listener position in world units
	Forward  lin.Vec3 // facing direction; zero means (0, 0, -1) in SetListener
	Up       lin.Vec3 // head-up direction; zero means (0, 1, 0) in SetListener
	Velocity lin.Vec3 // world units per second for Doppler
}
```

Listener is where positional voices are heard from. Forward and Up orient it; the right ear lies along Forward × Up. Velocity, in world units per second, only matters once Doppler is on (see SetDoppler).

<a id="Mixer"></a>

### Mixer

```go
type Mixer struct {
	// contains filtered or unexported fields
}
```

Mixer sums playing voices into the output stream.

Two locks divide the work. mu guards everything a game can change from its own goroutine, and is held only briefly: a setter takes it, and the mixer takes it twice a block, once to copy out what it is about to mix and once to write back where every voice reached. mixMu is held across the whole block and marks the playback state the mixer owns while it runs, so nothing else may move a voice through its sound mid-block. Streams are read with mu released and mixMu held. Stream.Read may call setters or start voices, but must not seek a voice because Seek also needs mixMu. Stream implementations must avoid lock cycles with callers on other goroutines.

Example:

```go
package main

import (
	"fmt"

	"github.com/matjam/bunyip/audio"
)

func main() {
	// The engine creates the mixer and the output device pulls frames
	// from it; a game only starts and adjusts voices.
	m := audio.NewMixer(48000)
	beep, _ := m.NewSound(audio.Sine(440, 0.1, 48000))
	v := m.Play(beep, audio.PlayOptions{Volume: 0.5, Pan: -0.5})
	fmt.Println(m.Playing(), v.Playing())
	m.StopAll()
	fmt.Println(m.Playing())
}
```

Output:

```
1 true
0
```

<a id="NewMixer"></a>

#### NewMixer

```go
func NewMixer(rate int) *Mixer
```

NewMixer makes a mixer for a positive output sample rate, with unity master gain and a 64-voice limit. It does not open an output device; the engine supplies and drives Context.Audio for normal games.

<a id="Mixer.Bus"></a>

#### Mixer.Bus

```go
func (m *Mixer) Bus(name string) *Bus
```

Bus looks a bus up by name, or returns nil when none has it.

<a id="Mixer.Buses"></a>

#### Mixer.Buses

```go
func (m *Mixer) Buses() []*Bus
```

Buses returns the mixer's buses in the order they were made, starting with music, effects and dialogue, for a mixing panel that shows them all.

<a id="Mixer.CloseOutput"></a>

#### Mixer.CloseOutput

```go
func (m *Mixer) CloseOutput()
```

CloseOutput stops and releases this mixer's output. Voices remain available for a later SetOutputDevice call. Repeated and concurrent calls are safe. Do not call it from an audio callback; see SetOutputDevice.

<a id="Mixer.Dialogue"></a>

#### Mixer.Dialogue

```go
func (m *Mixer) Dialogue() *Bus
```

Dialogue is the bus for speech, named "dialogue". The name avoids confusion with Voice, which is any playing sound.

<a id="Mixer.Effects"></a>

#### Mixer.Effects

```go
func (m *Mixer) Effects() *Bus
```

Effects is the bus for sound effects, named "effects".

<a id="Mixer.InputDevices"></a>

#### Mixer.InputDevices

```go
func (m *Mixer) InputDevices() ([]DeviceInfo, error)
```

InputDevices lists capture endpoints without starting recording or requesting microphone access. Explicit NoAudio and headless runs return ErrNoDevice.

<a id="Mixer.Listener"></a>

#### Mixer.Listener

```go
func (m *Mixer) Listener() Listener
```

Listener returns the current listener.

<a id="Mixer.Music"></a>

#### Mixer.Music

```go
func (m *Mixer) Music() *Bus
```

Music is the bus for soundtrack voices, named "music".

<a id="Mixer.NewBus"></a>

#### Mixer.NewBus

```go
func (m *Mixer) NewBus(name string) *Bus
```

NewBus makes a named bus; the name is how Mixer.Bus finds it again. If a bus with that name already exists, it is returned instead.

<a id="Mixer.NewSound"></a>

#### Mixer.NewSound

```go
func (m *Mixer) NewSound(p PCM) (*Sound, error)
```

NewSound converts decoded PCM to the mixer's format: stereo at the mixer rate, resampled linearly when the rates differ. It copies p.Samples, which must contain a whole number of mono or stereo frames. Unsupported channel counts or invalid PCM return an error.

<a id="Mixer.OpenCapture"></a>

#### Mixer.OpenCapture

```go
func (m *Mixer) OpenCapture(opts CaptureOptions) (*Capture, error)
```

OpenCapture starts recording from DeviceID or the system default input. It returns an error when the run has no audio device, when the operating system has no capture backend, or when the device refuses to open, which is what happens on a machine with no microphone and on macOS when the player has not granted microphone access. Close the Capture when the game is done with it.

The stream is separate from the mixer: what it records is not played back unless the game plays it.

<a id="Mixer.OpenMusic"></a>

#### Mixer.OpenMusic

```go
func (m *Mixer) OpenMusic(r io.ReadSeeker, loop bool) (*Music, error)
```

OpenMusic prepares a file for streaming; loop makes it start over at the end. It sniffs the format from the start of r and waits for the first decoded chunk or an error. The reader must support seeking and remain available until Music.Close returns. Music does not close r.

<a id="Mixer.OpenMusicFile"></a>

#### Mixer.OpenMusicFile

```go
func (m *Mixer) OpenMusicFile(path string, loop bool) (*Music, error)
```

OpenMusicFile opens a WAV, Ogg Vorbis, MP3 or FLAC file for streaming. Music owns the file and closes it after the decoder stops. Failure closes the file before returning. Loop restarts playback at the end.

<a id="Mixer.OutputDevice"></a>

#### Mixer.OutputDevice

```go
func (m *Mixer) OutputDevice() (DeviceInfo, bool)
```

OutputDevice reports the chosen endpoint and whether this mixer owns an output. Empty ID with Default true means system-default routing; it does not identify the physical destination, which the OS may change. The result is selection state, not a live device-health query.

<a id="Mixer.OutputDevices"></a>

#### Mixer.OutputDevices

```go
func (m *Mixer) OutputDevices() ([]DeviceInfo, error)
```

OutputDevices lists available playback endpoints without opening a stream. Explicit NoAudio and headless runs return ErrNoDevice.

<a id="Mixer.Paused"></a>

#### Mixer.Paused

```go
func (m *Mixer) Paused() bool
```

Paused reports whether the whole mixer is paused.

<a id="Mixer.Play"></a>

#### Mixer.Play

```go
func (m *Mixer) Play(s *Sound, opts PlayOptions) *Voice
```

Play starts a sound made for this mixer's rate and returns its voice. If all available voices have higher priority, the returned voice is already finished. A nil or empty sound ends when the mixer next reads it.

<a id="Mixer.PlayStream"></a>

#### Mixer.PlayStream

```go
func (m *Mixer) PlayStream(s Stream, opts PlayOptions) *Voice
```

PlayStream starts a stream as a voice. Pitch and Doppler resample its frames continuously across blocks. Loop has no effect: a looping stream loops itself. The voice buffers up to 513 source frames of lookahead. The mixer does not close the stream when the voice ends. The caller owns its lifetime and must not share a stateful stream between voices unless the stream explicitly supports that use.

<a id="Mixer.Playing"></a>

#### Mixer.Playing

```go
func (m *Mixer) Playing() int
```

Playing counts active voices. A voice that has been stopped is not counted while its last millisecond ramps out.

<a id="Mixer.Rate"></a>

#### Mixer.Rate

```go
func (m *Mixer) Rate() int
```

Rate is the sample rate sounds are stored and mixed at.

<a id="Mixer.Record"></a>

#### Mixer.Record

```go
func (m *Mixer) Record(opts RecordOptions) (*Recorder, error)
```

Record starts an explicit bounded in-memory recording. Reaching MaxDuration is a normal stop; dropped capture samples are an error. The caller owns it.

<a id="Mixer.RecordPCM"></a>

#### Mixer.RecordPCM

```go
func (m *Mixer) RecordPCM(w io.Writer, opts RecordOptions) (*Recording, error)
```

RecordPCM streams interleaved signed 16-bit little-endian PCM without a container header. Rate and channels come from opts; the writer is borrowed. The worker can block in Write; its owner must unblock a blocked writer before Stop/Close can finish. Do not call Stop/Close from the writer's callback.

<a id="Mixer.RecordWAV"></a>

#### Mixer.RecordWAV

```go
func (m *Mixer) RecordWAV(w io.WriteSeeker, opts RecordOptions) (*Recording, error)
```

RecordWAV streams 16-bit PCM WAV at the borrowed writer's current offset. Stop finalizes the header and leaves the writer positioned after the WAV. The writer stays open. Its seek/write calls must not call Stop or Close. The configured maximum must fit RIFF's 32-bit size limit.

<a id="Mixer.RecordWAVFile"></a>

#### Mixer.RecordWAVFile

```go
func (m *Mixer) RecordWAVFile(path string, opts RecordOptions) (*Recording, error)
```

RecordWAVFile creates or replaces path and records 16-bit PCM WAV to it. It owns the file, finalizing and closing it on stop, including error paths.

<a id="Mixer.Reverb"></a>

#### Mixer.Reverb

```go
func (m *Mixer) Reverb() ReverbSettings
```

Reverb reports the settings the shared reverb is using now: those given to SetReverb, or the zone the listener is in blended by how far inside it stands. Wet is 0 when the reverb is off.

<a id="Mixer.SetDoppler"></a>

#### Mixer.SetDoppler

```go
func (m *Mixer) SetDoppler(factor float32)
```

SetDoppler turns the Doppler effect on for positional sounds: a source closing on the listener plays sharp, one receding plays flat, by how fast each moves along the line between them. The factor scales the effect; 1 is physical, 0 (the default) is off. Speeds come from Listener.Velocity and Voice.SetVelocity, in world units per second, against the speed of sound (see SetSpeedOfSound). Streams have no pitch, so Doppler leaves them alone.

<a id="Mixer.SetListener"></a>

#### Mixer.SetListener

```go
func (m *Mixer) SetListener(l Listener)
```

SetListener places the listener, usually at the camera each frame. Moving it also decides which ReverbZone, if any, the listener is in.

<a id="Mixer.SetListener2D"></a>

#### Mixer.SetListener2D

```go
func (m *Mixer) SetListener2D(x, y float32)
```

SetListener2D places the listener for a 2D game whose y axis points down the screen: sounds to the right on screen come from the right.

Example:

```go
package main

import (
	"fmt"

	"github.com/matjam/bunyip/audio"
	"github.com/matjam/bunyip/lin"
)

func main() {
	// Positional voices pan and fade by where they are relative to the
	// listener; in a 2D game put the listener at the camera each frame.
	m := audio.NewMixer(48000)
	m.SetListener2D(400, 300)
	tone, _ := m.NewSound(audio.Sine(330, 1, 48000))
	v := m.Play(tone, audio.PlayOptions{Loop: true, Positional: true, Position: lin.V3(600, 300, 0), MaxDistance: 800})
	v.SetPosition(lin.V3(200, 300, 0)) // now to the left
	fmt.Println(v.Playing())
}
```

Output:

```
true
```

<a id="Mixer.SetMasterVolume"></a>

#### Mixer.SetMasterVolume

```go
func (m *Mixer) SetMasterVolume(v float32)
```

SetMasterVolume scales every voice; 1 is unity.

<a id="Mixer.SetMaxVoices"></a>

#### Mixer.SetMaxVoices

```go
func (m *Mixer) SetMaxVoices(n int)
```

SetMaxVoices caps how many voices play at once (default 64). When the mixer is full, a new voice takes the place of the quietest voice of the lowest priority no higher than its own, or is refused. The stolen voice leaves the count at once and ramps out over the next millisecond, so the mixer may briefly render one more voice than the cap.

<a id="Mixer.SetOutputDevice"></a>

#### Mixer.SetOutputDevice

```go
func (m *Mixer) SetOutputDevice(id string) error
```

SetOutputDevice opens id and switches this mixer to it. Empty selects the system default routing endpoint. Failure leaves the previous output active. A successful switch can briefly produce silence; voices keep their positions. NewMixer stays device-free until this is explicitly called. The engine opens the default itself, except in NoAudio/headless runs, which return ErrNoDevice. Calls serialize with CloseOutput. Do not call either from Stream.Read or an audio callback (including OnDone): switching waits for device callbacks to end.

<a id="Mixer.SetPaused"></a>

#### Mixer.SetPaused

```go
func (m *Mixer) SetPaused(p bool)
```

SetPaused holds every voice on every bus, for when the game loses focus or a pause menu opens; the engine calls it, so a game rarely has to. The block in which the pause lands fades out and the block after the resume fades back in, so neither clicks. Bus and voice pauses are kept separately, so resuming the mixer leaves them as they were.

<a id="Mixer.SetReverb"></a>

#### Mixer.SetReverb

```go
func (m *Mixer) SetReverb(s ReverbSettings)
```

SetReverb configures the mixer's shared reverb: the room the listener is in when no ReverbZone says otherwise. Voices with a Reverb send add their signal to it (unless their bus has a reverb of its own); the tail is mixed on top of the dry output. The zero value turns it off.

Example:

```go
package main

import (
	"fmt"

	"github.com/matjam/bunyip/audio"
)

func main() {
	m := audio.NewMixer(48000)
	m.SetReverb(audio.ReverbSettings{RoomSize: 0.7, Wet: 0.3})
	hit, _ := m.NewSound(audio.Sine(200, 0.05, 48000))
	m.Play(hit, audio.PlayOptions{Reverb: 1, LowPass: 2000, Pitch: 0.9})
	fmt.Println(m.Playing())
}
```

Output:

```
1
```

<a id="Mixer.SetReverbZones"></a>

#### Mixer.SetReverbZones

```go
func (m *Mixer) SetReverbZones(zones []ReverbZone)
```

SetReverbZones replaces the set of reverb zones. The mixer checks them against the listener whenever it moves, so a game sets them once per level and moves the listener each frame. Pass nil to clear them.

<a id="Mixer.SetSpatial"></a>

#### Mixer.SetSpatial

```go
func (m *Mixer) SetSpatial(s SpatialSettings)
```

SetSpatial chooses how positional voices are placed between the ears. The zero value restores the constant-power pan law, which is the default. The change takes effect at the start of the next mixed block and every parameter is interpolated across it, so switching mode or moving the listener while sounds play does not click.

<a id="Mixer.SetSpeedOfSound"></a>

#### Mixer.SetSpeedOfSound

```go
func (m *Mixer) SetSpeedOfSound(c float32)
```

SetSpeedOfSound sets the speed Doppler works against, in the game's world units per second. The default is 343, right for metres; a game in pixels or larger units raises it to keep the effect subtle.

<a id="Mixer.Spatial"></a>

#### Mixer.Spatial

```go
func (m *Mixer) Spatial() SpatialSettings
```

Spatial reports the spatial settings, as given to SetSpatial.

<a id="Mixer.StopAll"></a>

#### Mixer.StopAll

```go
func (m *Mixer) StopAll()
```

StopAll silences every voice. Each one frees its slot at once and ramps out over the next millisecond, so nothing clicks.

<a id="Mixer.Voices"></a>

#### Mixer.Voices

```go
func (m *Mixer) Voices() []VoiceInfo
```

Voices returns a snapshot of the voices retained by the mixer, in the order the mixer holds them, for a debug view or a test. The values are copies: changing them changes nothing, and a voice may end before the caller reads them. Stopped voices can remain here until the next blocks retire their ramps, so the length need not equal Playing. Settings are copied under the settings lock; positions are read afterward from the last mixed block and need not represent the exact same instant.

<a id="Music"></a>

### Music

```go
type Music struct {
	// contains filtered or unexported fields
}
```

Music plays WAV, Ogg Vorbis, MP3 or FLAC through a two-second PCM buffer filled on a decoder goroutine. Ogg, MP3 and FLAC decode incrementally; WAV is decoded completely at open and retained in memory. Open one with Mixer.OpenMusicFile or Mixer.OpenMusic, start it with Mixer.PlayStream, and Close it when the game is done with it.

<a id="Music.Buffered"></a>

#### Music.Buffered

```go
func (mu *Music) Buffered() float64
```

Buffered reports how many seconds are decoded and waiting to play.

<a id="Music.Close"></a>

#### Music.Close

```go
func (mu *Music) Close()
```

Close stops and joins the decoder, then closes an owned file. Subsequent Read calls return zero; a voice ends when it next reads. Repeated and concurrent calls are safe. Readers supplied to OpenMusic remain borrowed and may be closed after this returns. Close waits for an in-flight Read or Seek, which a custom reader must unblock itself. Do not call Close from that reader or decoder, including its callbacks.

<a id="Music.Duration"></a>

#### Music.Duration

```go
func (mu *Music) Duration() float64
```

Duration is the track's length in seconds, or 0 when unknown. WAV always knows it. Ogg Vorbis reads it from the last page at open; MP3 scans the frame headers at open. Either reports 0 when its reader could not seek to find out. FLAC uses STREAMINFO's sample count, which may also be unknown (0).

<a id="Music.Err"></a>

#### Music.Err

```go
func (mu *Music) Err() error
```

Err reports a decoding error, if one ended the music early.

<a id="Music.LoopRange"></a>

#### Music.LoopRange

```go
func (mu *Music) LoopRange() (start, end time.Duration)
```

LoopRange returns configured, source-frame-aligned boundaries; (0,0) means the whole track. Values round up to a nanosecond so passing the result back to SetLoopRange preserves source-frame boundaries.

<a id="Music.Looping"></a>

#### Music.Looping

```go
func (mu *Music) Looping() bool
```

Looping reports whether Music repeats. This is independent of whether any Voice is currently playing the music.

<a id="Music.Read"></a>

#### Music.Read

```go
func (mu *Music) Read(out []float32) int
```

Read implements Stream for the mixer: it hands over buffered frames, pads a momentary shortfall with silence, and returns short only once the music has ended.

<a id="Music.Seek"></a>

#### Music.Seek

```go
func (mu *Music) Seek(seconds float64) error
```

Seek moves playback to seconds from the start. It returns at once and the decoder catches up on its goroutine, so a few milliseconds of silence can precede the new position; anything buffered from the old position is dropped. Seeking past the end ends the music, or starts it over when it loops. A decoder that fails to seek ends the music and reports through Err. WAV and Ogg Vorbis seek exactly; MP3 decodes from the previous frame boundary. FLAC seeks exactly by scanning frames, rewinding when necessary, with memory bounded to one decoded frame. Prefer Voice.Seek on a playing voice so its Position follows. Music whose voice has already ended can be sought and played again with PlayStream.

<a id="Music.SetLoopRange"></a>

#### Music.SetLoopRange

```go
func (mu *Music) SetLoopRange(start, end time.Duration) error
```

SetLoopRange selects \[start,end) and restarts decoding at start, flushing prefetched audio. (0,0) restores the whole track. Nonzero ranges must contain at least one source frame and fit a known duration; unknown-length music accepts a finite range and also wraps at early EOF. Boundaries round down to source frames. The range repeats only while Looping; without looping playback continues from start to the file's end.

<a id="Music.SetLooping"></a>

#### Music.SetLooping

```go
func (mu *Music) SetLooping(loop bool)
```

SetLooping changes repetition and flushes prefetched samples at the next playback position, accounting for voice lookahead. Enabling it after all samples reach EOF restarts at the loop start; a Voice that already stopped must be played again. Closed or failed music is unchanged. The next read may briefly underrun.

<a id="PCM"></a>

<a id="PCM.Samples"></a>

<a id="PCM.Channels"></a>

<a id="PCM.Rate"></a>

### PCM

```go
type PCM struct {
	Samples  []float32 // interleaved channel samples, nominally -1..1
	Channels int       // samples per frame; NewSound accepts 1 or 2
	Rate     int       // frames per second; must be positive
}
```

PCM is decoded audio of any channel count and rate, interleaved.

<a id="Decode"></a>

#### Decode

```go
func Decode(data []byte) (PCM, error)
```

Decode picks a sampled-audio decoder (WAV, Ogg Vorbis, MP3, FLAC) from the data's magic bytes. Tracker modules are music, not clips; see PlayModule.

<a id="DecodeFLAC"></a>

#### DecodeFLAC

```go
func DecodeFLAC(data []byte) (pcm PCM, err error)
```

DecodeFLAC decodes a native FLAC stream into interleaved float32 PCM. It supports 1..8 channels and 4..24-bit integer samples. Sound and Music playback accept mono or stereo. Frame CRCs are checked; the optional whole-stream MD5 is not verified. Ogg-encapsulated FLAC is not supported.

<a id="DecodeMP3"></a>

#### DecodeMP3

```go
func DecodeMP3(data []byte) (pcm PCM, err error)
```

DecodeMP3 decodes a whole MP3 file into memory as stereo PCM.

Encode game audio at 44.1 or 48 kHz: the decoder reproduces MPEG-2 low-sample-rate files (22.05 kHz and below) with an uneven level.

<a id="DecodeOGG"></a>

#### DecodeOGG

```go
func DecodeOGG(data []byte) (pcm PCM, err error)
```

DecodeOGG decodes a whole Ogg Vorbis file into memory.

<a id="DecodeWAV"></a>

#### DecodeWAV

```go
func DecodeWAV(data []byte) (PCM, error)
```

DecodeWAV reads PCM (8/16/24/32-bit integer or 32-bit float) WAV data.

<a id="Sine"></a>

#### Sine

```go
func Sine(freq float64, seconds float64, rate int) PCM
```

Sine synthesises a tone, handy for tests and placeholder effects.

<a id="PCM.SaveWAV"></a>

#### PCM.SaveWAV

```go
func (p PCM) SaveWAV(path string) (err error)
```

SaveWAV creates or replaces path with 16-bit PCM WAV and closes the file.

<a id="PCM.WriteWAV"></a>

#### PCM.WriteWAV

```go
func (p PCM) WriteWAV(w io.Writer) error
```

WriteWAV writes interleaved signed 16-bit PCM WAV to a borrowed writer. Samples are rounded and clamped to \[-1,1]; non-finite samples and invalid PCM are rejected before writing. RIFF files must fit the 32-bit size limit. The writer is never closed.

<a id="PlayOptions"></a>

<a id="PlayOptions.Volume"></a>

<a id="PlayOptions.Pan"></a>

<a id="PlayOptions.Loop"></a>

<a id="PlayOptions.Pitch"></a>

<a id="PlayOptions.Priority"></a>

<a id="PlayOptions.FadeIn"></a>

<a id="PlayOptions.Reverb"></a>

<a id="PlayOptions.LowPass"></a>

<a id="PlayOptions.Bus"></a>

<a id="PlayOptions.Occlusion"></a>

<a id="PlayOptions.Positional"></a>

<a id="PlayOptions.Position"></a>

<a id="PlayOptions.Velocity"></a>

<a id="PlayOptions.MinDistance"></a>

<a id="PlayOptions.MaxDistance"></a>

<a id="PlayOptions.RelativeToListener"></a>

<a id="PlayOptions.Direction"></a>

<a id="PlayOptions.Cone"></a>

<a id="PlayOptions.Attenuation"></a>

### PlayOptions

```go
type PlayOptions struct {
	Volume   float32 // 1
	Pan      float32 // -1 left .. +1 right; ignored when Positional
	Loop     bool    // repeat a Sound; streams control their own looping
	Pitch    float32 // playback rate for sounds and streams; default 1, clamped to 0.01..64
	Priority int     // higher survives when the mixer is full
	FadeIn   float32 // seconds to rise from silence
	Reverb   float32 // 0..1 send into the bus's reverb, or the mixer's (see SetReverb)
	LowPass  float32 // low-pass cutoff in Hz; 0 leaves the sound unfiltered
	Bus      *Bus    // bus the voice plays through; nil is the master alone

	// Occlusion is how much of the world is between the source and the
	// listener: 0 clear, 1 fully blocked, which drops the voice 20 dB and
	// muffles it to 400 Hz. A game sets it from a physics ray.
	Occlusion float32

	// Positional voices are heard from Position relative to the listener:
	// full volume within MinDistance (1), fading to silence at
	// MaxDistance (100), panned by direction. Velocity, in world units per
	// second, drives Doppler once SetDoppler turns it on.
	Positional         bool
	Position           lin.Vec3    // source position in listener world units
	Velocity           lin.Vec3    // source velocity in world units per second
	MinDistance        float32     // full-volume radius; zero means 1
	MaxDistance        float32     // silent at this distance; zero means 100
	RelativeToListener bool        // listener-local position/direction/velocity; also enables Positional
	Direction          lin.Vec3    // cone direction; nonzero enables Positional; zero or invalid means (0,0,-1)
	Cone               Cone        // nonzero enables Positional; zero is omnidirectional; invalid values use the default
	Attenuation        Attenuation // nonzero enables Positional; zero preserves the original curve; invalid values use the default
}
```

PlayOptions shape a voice at start. Zero values mean the defaults noted.

<a id="PlaybackState"></a>

### PlaybackState

```go
type PlaybackState uint8
```

PlaybackState is a voice's effective playback state, including pauses applied by its bus or mixer. Muting does not pause playback.

<a id="PlaybackStopped"></a>

<a id="PlaybackPlaying"></a>

<a id="PlaybackPaused"></a>

```go
const (
	PlaybackStopped PlaybackState = iota // ended or explicitly stopped
	PlaybackPlaying                      // advancing, even while muted or inaudible
	PlaybackPaused                       // held by the voice, its bus or the mixer
)
```

<a id="PlaybackState.String"></a>

#### PlaybackState.String

```go
func (s PlaybackState) String() string
```

String returns "stopped", "playing", "paused", or "unknown".

<a id="RecordOptions"></a>

<a id="RecordOptions.Capture"></a>

<a id="RecordOptions.MaxDuration"></a>

### RecordOptions

```go
type RecordOptions struct {
	Capture     CaptureOptions
	MaxDuration time.Duration // positive wall-clock and source-frame limit; zero means 30 seconds
}
```

RecordOptions bounds an explicit recording. Zero Capture values use the mixer's rate, mono, default input and a half-second capture ring.

<a id="Recorder"></a>

### Recorder

```go
type Recorder struct {
	// contains filtered or unexported fields
}
```

Recorder owns a capture stream and worker retaining a bounded PCM recording. It stops automatically at MaxDuration. Stop or Close joins the worker.

<a id="Recorder.Close"></a>

#### Recorder.Close

```go
func (r *Recorder) Close() error
```

Close joins the recording. PCM remains available from later Stop calls.

<a id="Recorder.Done"></a>

#### Recorder.Done

```go
func (r *Recorder) Done() <-chan struct{}
```

Done closes after capture, output finalization and the worker have finished.

<a id="Recorder.Err"></a>

#### Recorder.Err

```go
func (r *Recorder) Err() error
```

Err reports the terminal recording error after Done closes; before then nil.

<a id="Recorder.Stop"></a>

#### Recorder.Stop

```go
func (r *Recorder) Stop() (PCM, error)
```

Stop stops capture, drains accepted buffered frames up to MaxDuration, and joins the worker. It returns an independent PCM copy, including partial data on error. Repeated calls return equivalent independent copies.

<a id="Recording"></a>

### Recording

```go
type Recording struct {
	// contains filtered or unexported fields
}
```

Recording owns a capture stream and worker writing to an output. It retains only capture and conversion buffers, not the whole recording.

<a id="Recording.Close"></a>

#### Recording.Close

```go
func (r *Recording) Close() error
```

Close is equivalent to Stop and satisfies io.Closer.

<a id="Recording.Done"></a>

#### Recording.Done

```go
func (r *Recording) Done() <-chan struct{}
```

Done closes when capture and output processing have finished.

<a id="Recording.Err"></a>

#### Recording.Err

```go
func (r *Recording) Err() error
```

Err reports the terminal recording error after Done closes; before then nil.

<a id="Recording.Stop"></a>

#### Recording.Stop

```go
func (r *Recording) Stop() error
```

Stop stops capture, drains accepted frames within the limit and joins the output worker, including WAV finalization and owned-file closure.

<a id="ReverbSettings"></a>

<a id="ReverbSettings.RoomSize"></a>

<a id="ReverbSettings.Damping"></a>

<a id="ReverbSettings.Width"></a>

<a id="ReverbSettings.Wet"></a>

### ReverbSettings

```go
type ReverbSettings struct {
	RoomSize float32 // 0..1, how long the tail rings
	Damping  float32 // 0..1, how quickly highs die away
	Width    float32 // 0..1, stereo spread
	Wet      float32 // 0..1, level of the reverb in the mix
}
```

ReverbSettings describe a reverb, which voices feed through their Reverb send. Zero RoomSize, Damping and Width take the defaults 0.5, 0.5 and 1; a zero Wet turns the reverb off, so the zero value is no reverb. Every field runs from 0 to 1 and values outside that are clamped when the reverb uses them, because a room larger than 1 has feedback that never decays.

<a id="ReverbZone"></a>

<a id="ReverbZone.Center"></a>

<a id="ReverbZone.Radius"></a>

<a id="ReverbZone.Fade"></a>

<a id="ReverbZone.Settings"></a>

### ReverbZone

```go
type ReverbZone struct {
	Center   lin.Vec3       // sphere centre in listener world units
	Radius   float32        // sphere radius; nonpositive zones are ignored
	Fade     float32        // inward blend distance; nonpositive means Radius
	Settings ReverbSettings // reverb at full zone strength
}
```

ReverbZone is a region of the world with its own reverb: a cave, a hall, a tunnel. While the listener is inside the sphere the zone's Settings replace the mixer's shared reverb, blended in over Fade units from the edge so walking in never jumps. A zero Fade blends across the whole radius, so the zone is at full strength only at its centre; set Fade to a fraction of Radius for a room that sounds the same everywhere but its doorway. Where zones overlap, the one the listener is furthest inside wins.

<a id="Seeker"></a>

<a id="Seeker.Seek"></a>

### Seeker

```go
type Seeker interface {
	Stream
	// Seek moves playback to seconds from the start.
	Seek(seconds float64) error
}
```

Seeker is a Stream that can jump to a time, as Music can.

<a id="Sound"></a>

### Sound

```go
type Sound struct {
	// contains filtered or unexported fields
}
```

Sound is a PCM clip converted to the mixer's stereo rate.

<a id="Sound.Duration"></a>

#### Sound.Duration

```go
func (s *Sound) Duration() float64
```

Duration is the sound's length in seconds.

<a id="Sound.Frames"></a>

#### Sound.Frames

```go
func (s *Sound) Frames() int
```

Frames is the sound's length in frames.

<a id="Sound.SaveWAV"></a>

#### Sound.SaveWAV

```go
func (s *Sound) SaveWAV(path string) error
```

SaveWAV creates or replaces path with this sound's stereo PCM WAV.

<a id="Sound.WriteWAV"></a>

#### Sound.WriteWAV

```go
func (s *Sound) WriteWAV(w io.Writer) error
```

WriteWAV exports this sound at its mixer rate as stereo 16-bit PCM WAV. The writer remains borrowed; see PCM.WriteWAV.

<a id="SpatialSettings"></a>

<a id="SpatialSettings.Binaural"></a>

<a id="SpatialSettings.HeadRadius"></a>

### SpatialSettings

```go
type SpatialSettings struct {
	// Binaural renders each positional voice through a head model
	// instead: an interaural time difference, an interaural level
	// difference with head shadow, and an elevation cue. It suits
	// headphones; on speakers the two ears mix and the cues weaken.
	Binaural bool

	// HeadRadius is the modelled head's radius in metres, which sets how
	// far apart the ears are and so how large the time difference grows.
	// A zero radius means 0.0875, an average adult head; values above 0.3
	// are clamped.
	HeadRadius float32
}
```

SpatialSettings choose how a positional voice is placed between the listener's ears. The zero value pans by direction with a constant-power law, which is what a game gets without calling SetSpatial.

<a id="Stream"></a>

<a id="Stream.Read"></a>

### Stream

```go
type Stream interface {
	// Read fills out with len(out)/2 frames and reports how many frames it
	// wrote, between zero and len(out)/2; fewer than asked means the stream
	// has ended. For a temporary underrun, fill the remainder with silence
	// and report the full frame count to keep the voice alive.
	Read(out []float32) int
}
```

Stream produces stereo float32 frames on demand at the mixer's rate; it is how music and synthesised sound play without being decoded up front. Read runs on the mixing thread and must return promptly. It may call mixer setters or Play, but must not call Voice.Seek. Do not retain out.

<a id="Voice"></a>

### Voice

```go
type Voice struct {
	// contains filtered or unexported fields
}
```

Voice is one playing sound or stream.

<a id="Voice.Attenuation"></a>

#### Voice.Attenuation

```go
func (v *Voice) Attenuation() Attenuation
```

Attenuation returns the effective distance model and rolloff.

<a id="Voice.Cone"></a>

#### Voice.Cone

```go
func (v *Voice) Cone() Cone
```

Cone returns the effective cone; the default has full-circle angles and unity gain.

<a id="Voice.Direction"></a>

#### Voice.Direction

```go
func (v *Voice) Direction() lin.Vec3
```

Direction returns the normalized cone direction in the selected coordinate space.

<a id="Voice.DistanceRange"></a>

#### Voice.DistanceRange

```go
func (v *Voice) DistanceRange() (minDistance, maxDistance float32)
```

DistanceRange returns the full-volume and silence distances.

<a id="Voice.FadeOut"></a>

#### Voice.FadeOut

```go
func (v *Voice) FadeOut(seconds float32)
```

FadeOut fades to silence over seconds and then stops the voice. The stop ramps out whatever level the last block of the fade reached, so a fade shorter than one block does not click.

<a id="Voice.FadeTo"></a>

#### Voice.FadeTo

```go
func (v *Voice) FadeTo(vol, seconds float32)
```

FadeTo moves the volume to vol over seconds.

<a id="Voice.Muted"></a>

#### Voice.Muted

```go
func (v *Voice) Muted() bool
```

Muted reports whether the voice is muted.

<a id="Voice.Occlusion"></a>

#### Voice.Occlusion

```go
func (v *Voice) Occlusion() float32
```

Occlusion reports the voice's occlusion amount.

<a id="Voice.OnDone"></a>

#### Voice.OnDone

```go
func (v *Voice) OnDone(fn func())
```

OnDone registers fn to run when the voice ends, whether it played out, was stopped, faded out, or lost its slot to a higher priority voice. A voice that played out or was stopped with Voice.Stop calls fn on the mixer's thread, usually the audio device's, after the mixer has released its lock; StopAll and a stolen slot call it on the calling goroutine, before the millisecond of ramp that follows is mixed. A voice that has already ended runs fn at once on the calling goroutine. fn may start another voice, but it must return quickly and must not block. Only the last fn registered runs.

Example:

```go
package main

import (
	"fmt"

	"github.com/matjam/bunyip/audio"
)

func main() {
	m := audio.NewMixer(48000)
	beep, _ := m.NewSound(audio.Sine(440, 0.01, 48000))
	v := m.Play(beep, audio.PlayOptions{})
	v.OnDone(func() { fmt.Println("done") }) // when it plays out, or is stopped
	m.StopAll()
}
```

Output:

```
done
```

<a id="Voice.Playing"></a>

#### Voice.Playing

```go
func (v *Voice) Playing() bool
```

Playing reports whether the voice is active, including while paused, muted or outside its audible range. A stopped voice reports false as soon as it is stopped, while its last millisecond ramps out.

<a id="Voice.Position"></a>

#### Voice.Position

```go
func (v *Voice) Position() float64
```

Position is how far into the sound the voice is, in seconds. For a stream it counts source-rate time since the voice started or last sought, including pitch, Doppler and underrun silence, without wrapping at stream loop boundaries. Direct Music controls do not reset it. It reads the position the last finished block reached, so it takes no lock and never waits on the mixer.

<a id="Voice.RelativeToListener"></a>

#### Voice.RelativeToListener

```go
func (v *Voice) RelativeToListener() bool
```

RelativeToListener reports whether source coordinates follow the listener.

<a id="Voice.Seek"></a>

#### Voice.Seek

```go
func (v *Voice) Seek(seconds float64) error
```

Seek moves a sound voice to seconds from the start, clamped to the sound's length. A stream voice is moved when its stream is a Seeker; otherwise it stays put and ErrNotSeekable is returned. Seeking waits for the block being mixed to finish, because it moves the position the mixer is reading from, so it may block for the length of one block; a Seeker must not recursively call Voice.Seek. Do not call Seek from Stream.Read, which runs under the same playback lock.

<a id="Voice.SetAttenuation"></a>

#### Voice.SetAttenuation

```go
func (v *Voice) SetAttenuation(attenuation Attenuation) error
```

SetAttenuation enables positional audio and changes the distance model. Invalid values leave it unchanged.

<a id="Voice.SetCone"></a>

#### Voice.SetCone

```go
func (v *Voice) SetCone(cone Cone) error
```

SetCone enables positional audio and changes directionality. Invalid values leave it unchanged.

<a id="Voice.SetDirection"></a>

#### Voice.SetDirection

```go
func (v *Voice) SetDirection(direction lin.Vec3) error
```

SetDirection enables positional audio and sets a nonzero finite direction. It is in world coordinates, or listener-local when relative mode is on.

<a id="Voice.SetDistanceRange"></a>

#### Voice.SetDistanceRange

```go
func (v *Voice) SetDistanceRange(minDistance, maxDistance float32) error
```

SetDistanceRange enables positional audio with finite limits 0 \< min \< max.

<a id="Voice.SetLowPass"></a>

#### Voice.SetLowPass

```go
func (v *Voice) SetLowPass(cutoff float32)
```

SetLowPass sets the low-pass cutoff in Hz; 0 removes the filter.

<a id="Voice.SetMute"></a>

#### Voice.SetMute

```go
func (v *Voice) SetMute(mute bool)
```

SetMute silences the voice while it keeps playing, so unmuting resumes wherever the sound has reached. To stop the sound advancing, use SetPaused instead.

<a id="Voice.SetOcclusion"></a>

#### Voice.SetOcclusion

```go
func (v *Voice) SetOcclusion(o float32)
```

SetOcclusion sets how blocked the path from the source is: 0 clear, 1 fully blocked (20 dB down and muffled to 400 Hz), in between for a half-open door. A game sets it from a physics ray each frame; the gain ramps, so the change never clicks.

<a id="Voice.SetPan"></a>

#### Voice.SetPan

```go
func (v *Voice) SetPan(pan float32)
```

SetPan moves the voice between -1 (left) and +1 (right).

<a id="Voice.SetPaused"></a>

#### Voice.SetPaused

```go
func (v *Voice) SetPaused(p bool)
```

SetPaused holds the voice in place, silent, until resumed. The block the pause lands in fades out, so it never clicks.

<a id="Voice.SetPitch"></a>

#### Voice.SetPitch

```go
func (v *Voice) SetPitch(p float32)
```

SetPitch changes playback rate; 2 plays an octave up at double speed. Values are clamped to 0.01..64; zero or nonfinite values restore 1.

<a id="Voice.SetPosition"></a>

#### Voice.SetPosition

```go
func (v *Voice) SetPosition(p lin.Vec3)
```

SetPosition moves a positional voice.

<a id="Voice.SetRelativeToListener"></a>

#### Voice.SetRelativeToListener

```go
func (v *Voice) SetRelativeToListener(relative bool)
```

SetRelativeToListener selects listener-local source coordinates: +X is right, +Y up, -Z forward. Position, direction and velocity use this basis; listener translation/velocity are added. This enables positional audio.

<a id="Voice.SetReverb"></a>

#### Voice.SetReverb

```go
func (v *Voice) SetReverb(send float32)
```

SetReverb changes the send level into the bus's reverb, or the mixer's.

<a id="Voice.SetSolo"></a>

#### Voice.SetSolo

```go
func (v *Voice) SetSolo(solo bool)
```

SetSolo solos the voice. While any voice is soloed, every voice that is not soloed is silent and keeps playing. Clearing the last solo makes them audible again. Bus solos are separate and combine with it.

<a id="Voice.SetVelocity"></a>

#### Voice.SetVelocity

```go
func (v *Voice) SetVelocity(vel lin.Vec3)
```

SetVelocity sets a positional voice's velocity in world units per second, for Doppler. It only changes the pitch; the game moves the voice with SetPosition.

<a id="Voice.SetVolume"></a>

#### Voice.SetVolume

```go
func (v *Voice) SetVolume(vol float32)
```

SetVolume changes the voice's gain; 1 is unity.

<a id="Voice.Soloed"></a>

#### Voice.Soloed

```go
func (v *Voice) Soloed() bool
```

Soloed reports whether the voice is soloed.

<a id="Voice.State"></a>

#### Voice.State

```go
func (v *Voice) State() PlaybackState
```

State reports the effective state. A pause is reported immediately, including the block that fades out; Stop reports stopped during its ramp.

<a id="Voice.Stop"></a>

#### Voice.Stop

```go
func (v *Voice) Stop()
```

Stop ends the voice. The gain ramps to silence over about a millisecond first, so stopping mid-cycle never clicks; the voice frees its slot at once and OnDone runs when the ramp finishes.

<a id="VoiceInfo"></a>

<a id="VoiceInfo.Bus"></a>

<a id="VoiceInfo.Volume"></a>

<a id="VoiceInfo.Pan"></a>

<a id="VoiceInfo.Pitch"></a>

<a id="VoiceInfo.Seconds"></a>

<a id="VoiceInfo.Stream"></a>

<a id="VoiceInfo.Positional"></a>

<a id="VoiceInfo.Position"></a>

<a id="VoiceInfo.Loop"></a>

<a id="VoiceInfo.Paused"></a>

<a id="VoiceInfo.Muted"></a>

<a id="VoiceInfo.Soloed"></a>

<a id="VoiceInfo.Priority"></a>

<a id="VoiceInfo.Reverb"></a>

<a id="VoiceInfo.Occlusion"></a>

### VoiceInfo

```go
type VoiceInfo struct {
	// Bus is the name of the bus the voice plays through, empty when it
	// plays straight through the master.
	Bus string
	// Volume, Pan and Pitch are the voice's own settings, before the bus
	// and master gains.
	Volume, Pan, Pitch float32
	// Seconds is how far into the sound or stream the voice has played.
	Seconds float64
	// Stream is set for a voice playing a stream (music, a tracker
	// module) rather than a decoded sound.
	Stream bool
	// Positional voices are heard from Position in the listener's world;
	// the rest are panned.
	Positional bool
	Position   lin.Vec3 // source position in listener world units

	Loop      bool    // voice loop option; stream looping is controlled separately
	Paused    bool    // voice pause only, excluding bus and mixer pauses
	Muted     bool    // voice mute only, excluding bus mute and solo suppression
	Soloed    bool    // voice solo flag
	Priority  int     // priority used when choosing a voice to steal
	Reverb    float32 // send level into the bus or shared reverb
	Occlusion float32 // obstruction amount from 0 (clear) to 1 (blocked)
}
```

VoiceInfo is one playing voice as a debug view sees it: a snapshot, taken under the mixer's lock, of what the voice is doing now.
