Package github.com/matjam/bunyip/audio
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.
Index
- Variables
- type Attenuation
- type AttenuationModel
- type Bus
func (b *Bus) Muted() boolfunc (b *Bus) Name() stringfunc (b *Bus) Paused() boolfunc (b *Bus) SetMute(mute bool)func (b *Bus) SetPaused(p bool)func (b *Bus) SetReverb(s ReverbSettings)func (b *Bus) SetSolo(solo bool)func (b *Bus) SetVolume(v float32)func (b *Bus) Soloed() boolfunc (b *Bus) Volume() float32
- type Capture
- type CaptureOptions
- type Cone
- type DeviceInfo
- type Listener
- type Mixer
func NewMixer(rate int) *Mixerfunc (m *Mixer) Bus(name string) *Busfunc (m *Mixer) Buses() []*Busfunc (m *Mixer) CloseOutput()func (m *Mixer) Dialogue() *Busfunc (m *Mixer) Effects() *Busfunc (m *Mixer) InputDevices() ([]DeviceInfo, error)func (m *Mixer) Listener() Listenerfunc (m *Mixer) Music() *Busfunc (m *Mixer) NewBus(name string) *Busfunc (m *Mixer) NewSound(p PCM) (*Sound, error)func (m *Mixer) OpenCapture(opts CaptureOptions) (*Capture, error)func (m *Mixer) OpenMusic(r io.ReadSeeker, loop bool) (*Music, error)func (m *Mixer) OpenMusicFile(path string, loop bool) (*Music, error)func (m *Mixer) OutputDevice() (DeviceInfo, bool)func (m *Mixer) OutputDevices() ([]DeviceInfo, error)func (m *Mixer) Paused() boolfunc (m *Mixer) Play(s *Sound, opts PlayOptions) *Voicefunc (m *Mixer) PlayStream(s Stream, opts PlayOptions) *Voicefunc (m *Mixer) Playing() intfunc (m *Mixer) Rate() intfunc (m *Mixer) Record(opts RecordOptions) (*Recorder, error)func (m *Mixer) RecordPCM(w io.Writer, opts RecordOptions) (*Recording, error)func (m *Mixer) RecordWAV(w io.WriteSeeker, opts RecordOptions) (*Recording, error)func (m *Mixer) RecordWAVFile(path string, opts RecordOptions) (*Recording, error)func (m *Mixer) Reverb() ReverbSettingsfunc (m *Mixer) SetDoppler(factor float32)func (m *Mixer) SetListener(l Listener)func (m *Mixer) SetListener2D(x, y float32)func (m *Mixer) SetMasterVolume(v float32)func (m *Mixer) SetMaxVoices(n int)func (m *Mixer) SetOutputDevice(id string) errorfunc (m *Mixer) SetPaused(p bool)func (m *Mixer) SetReverb(s ReverbSettings)func (m *Mixer) SetReverbZones(zones []ReverbZone)func (m *Mixer) SetSpatial(s SpatialSettings)func (m *Mixer) SetSpeedOfSound(c float32)func (m *Mixer) Spatial() SpatialSettingsfunc (m *Mixer) StopAll()func (m *Mixer) Voices() []VoiceInfo
- type Music
func (mu *Music) Buffered() float64func (mu *Music) Close()func (mu *Music) Duration() float64func (mu *Music) Err() errorfunc (mu *Music) LoopRange() (start, end time.Duration)func (mu *Music) Looping() boolfunc (mu *Music) Read(out []float32) intfunc (mu *Music) Seek(seconds float64) errorfunc (mu *Music) SetLoopRange(start, end time.Duration) errorfunc (mu *Music) SetLooping(loop bool)
- type PCM
func Decode(data []byte) (PCM, error)func DecodeFLAC(data []byte) (pcm PCM, err error)func DecodeMP3(data []byte) (pcm PCM, err error)func DecodeOGG(data []byte) (pcm PCM, err error)func DecodeWAV(data []byte) (PCM, error)func Sine(freq float64, seconds float64, rate int) PCMfunc (p PCM) SaveWAV(path string) (err error)func (p PCM) WriteWAV(w io.Writer) error
- type PlayOptions
- type PlaybackState
- type RecordOptions
- type Recorder
- type Recording
- type ReverbSettings
- type ReverbZone
- type Seeker
- type Sound
- type SpatialSettings
- type Stream
- type Voice
func (v *Voice) Attenuation() Attenuationfunc (v *Voice) Cone() Conefunc (v *Voice) Direction() lin.Vec3func (v *Voice) DistanceRange() (minDistance, maxDistance float32)func (v *Voice) FadeOut(seconds float32)func (v *Voice) FadeTo(vol, seconds float32)func (v *Voice) Muted() boolfunc (v *Voice) Occlusion() float32func (v *Voice) OnDone(fn func())func (v *Voice) Playing() boolfunc (v *Voice) Position() float64func (v *Voice) RelativeToListener() boolfunc (v *Voice) Seek(seconds float64) errorfunc (v *Voice) SetAttenuation(attenuation Attenuation) errorfunc (v *Voice) SetCone(cone Cone) errorfunc (v *Voice) SetDirection(direction lin.Vec3) errorfunc (v *Voice) SetDistanceRange(minDistance, maxDistance float32) errorfunc (v *Voice) SetLowPass(cutoff float32)func (v *Voice) SetMute(mute bool)func (v *Voice) SetOcclusion(o float32)func (v *Voice) SetPan(pan float32)func (v *Voice) SetPaused(p bool)func (v *Voice) SetPitch(p float32)func (v *Voice) SetPosition(p lin.Vec3)func (v *Voice) SetRelativeToListener(relative bool)func (v *Voice) SetReverb(send float32)func (v *Voice) SetSolo(solo bool)func (v *Voice) SetVelocity(vel lin.Vec3)func (v *Voice) SetVolume(vol float32)func (v *Voice) Soloed() boolfunc (v *Voice) State() PlaybackStatefunc (v *Voice) Stop()
- type VoiceInfo
Variables
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.
var ErrDeviceUnavailable = audioout.ErrUnavailable
ErrDeviceUnavailable means the requested endpoint could not be opened.
var ErrDeviceUnsupported = audioout.ErrUnsupported
ErrDeviceUnsupported means this operating system or installation has no backend.
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.
var ErrNotSeekable = errors.New("audio: stream cannot seek")
ErrNotSeekable is returned by Voice.Seek for a stream that cannot seek.
Types
type Attenuation source
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.
type AttenuationModel source
type AttenuationModel uint8
AttenuationModel selects distance gain between a voice's distance limits.
type Bus source
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
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())
}
1 0.6
SetMute source
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.
SetPaused source
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.
SetReverb source
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.
SetSolo source
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.
type Capture source
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.
Buffered source
func (c *Capture) Buffered() int
Buffered is how many recorded samples are waiting to be read.
Channels source
func (c *Capture) Channels() int
Channels is how many channels each frame has; 1 is mono.
Close source
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.
Dropped source
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.
Level source
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.
Read source
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.
type CaptureOptions source
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.
type Cone source
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.
type DeviceInfo source
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.
type Listener source
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).
type Mixer source
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
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())
}
1 true
0
NewMixer source
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.
Bus source
func (m *Mixer) Bus(name string) *Bus
Bus looks a bus up by name, or returns nil when none has it.
Buses source
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.
CloseOutput source
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.
Dialogue source
func (m *Mixer) Dialogue() *Bus
Dialogue is the bus for speech, named "dialogue". The name avoids confusion with Voice, which is any playing sound.
Effects source
func (m *Mixer) Effects() *Bus
Effects is the bus for sound effects, named "effects".
InputDevices source
func (m *Mixer) InputDevices() ([]DeviceInfo, error)
InputDevices lists capture endpoints without starting recording or requesting microphone access. Explicit NoAudio and headless runs return ErrNoDevice.
NewBus source
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.
NewSound source
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.
OpenCapture source
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.
OpenMusic source
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.
OpenMusicFile source
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.
OutputDevice source
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.
OutputDevices source
func (m *Mixer) OutputDevices() ([]DeviceInfo, error)
OutputDevices lists available playback endpoints without opening a stream. Explicit NoAudio and headless runs return ErrNoDevice.
Play source
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.
PlayStream source
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.
Playing source
func (m *Mixer) Playing() int
Playing counts active voices. A voice that has been stopped is not counted while its last millisecond ramps out.
Record source
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.
RecordPCM source
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.
RecordWAV source
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.
RecordWAVFile source
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.
Reverb source
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.
SetDoppler source
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.
SetListener source
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.
SetListener2D source
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
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())
}
true
SetMasterVolume source
func (m *Mixer) SetMasterVolume(v float32)
SetMasterVolume scales every voice; 1 is unity.
SetMaxVoices source
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.
SetOutputDevice source
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.
SetPaused source
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.
SetReverb source
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
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())
}
1
SetReverbZones source
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.
SetSpatial source
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.
SetSpeedOfSound source
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.
Spatial source
func (m *Mixer) Spatial() SpatialSettings
Spatial reports the spatial settings, as given to SetSpatial.
StopAll source
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.
Voices source
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.
type Music source
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.
Buffered source
func (mu *Music) Buffered() float64
Buffered reports how many seconds are decoded and waiting to play.
Close source
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.
Duration source
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).
LoopRange source
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.
Looping source
func (mu *Music) Looping() bool
Looping reports whether Music repeats. This is independent of whether any Voice is currently playing the music.
Read source
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.
Seek source
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.
SetLoopRange source
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.
SetLooping source
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.
type PCM source
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.
Decode source
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.
DecodeFLAC source
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.
DecodeMP3 source
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.
DecodeOGG source
func DecodeOGG(data []byte) (pcm PCM, err error)
DecodeOGG decodes a whole Ogg Vorbis file into memory.
DecodeWAV source
func DecodeWAV(data []byte) (PCM, error)
DecodeWAV reads PCM (8/16/24/32-bit integer or 32-bit float) WAV data.
Sine source
func Sine(freq float64, seconds float64, rate int) PCM
Sine synthesises a tone, handy for tests and placeholder effects.
SaveWAV source
func (p PCM) SaveWAV(path string) (err error)
SaveWAV creates or replaces path with 16-bit PCM WAV and closes the file.
WriteWAV source
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.
type PlayOptions source
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.
type PlaybackState source
type PlaybackState uint8
PlaybackState is a voice's effective playback state, including pauses applied by its bus or mixer. Muting does not pause playback.
type RecordOptions source
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.
type Recorder source
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.
Close source
func (r *Recorder) Close() error
Close joins the recording. PCM remains available from later Stop calls.
Done source
func (r *Recorder) Done() <-chan struct{}
Done closes after capture, output finalization and the worker have finished.
type Recording source
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.
Done source
func (r *Recording) Done() <-chan struct{}
Done closes when capture and output processing have finished.
type ReverbSettings source
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.
type ReverbZone source
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.
type Seeker source
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.
type Sound source
type Sound struct {
// contains filtered or unexported fields
}
Sound is a PCM clip converted to the mixer's stereo rate.
type SpatialSettings source
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.
type Stream source
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.
type Voice source
type Voice struct {
// contains filtered or unexported fields
}
Voice is one playing sound or stream.
Attenuation source
func (v *Voice) Attenuation() Attenuation
Attenuation returns the effective distance model and rolloff.
Cone source
func (v *Voice) Cone() Cone
Cone returns the effective cone; the default has full-circle angles and unity gain.
Direction source
func (v *Voice) Direction() lin.Vec3
Direction returns the normalized cone direction in the selected coordinate space.
DistanceRange source
func (v *Voice) DistanceRange() (minDistance, maxDistance float32)
DistanceRange returns the full-volume and silence distances.
FadeOut source
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.
FadeTo source
func (v *Voice) FadeTo(vol, seconds float32)
FadeTo moves the volume to vol over seconds.
Occlusion source
func (v *Voice) Occlusion() float32
Occlusion reports the voice's occlusion amount.
OnDone source
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
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()
}
done
Playing source
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.
Position source
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.
RelativeToListener source
func (v *Voice) RelativeToListener() bool
RelativeToListener reports whether source coordinates follow the listener.
Seek source
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.
SetAttenuation source
func (v *Voice) SetAttenuation(attenuation Attenuation) error
SetAttenuation enables positional audio and changes the distance model. Invalid values leave it unchanged.
SetCone source
func (v *Voice) SetCone(cone Cone) error
SetCone enables positional audio and changes directionality. Invalid values leave it unchanged.
SetDirection source
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.
SetDistanceRange source
func (v *Voice) SetDistanceRange(minDistance, maxDistance float32) error
SetDistanceRange enables positional audio with finite limits 0 < min < max.
SetLowPass source
func (v *Voice) SetLowPass(cutoff float32)
SetLowPass sets the low-pass cutoff in Hz; 0 removes the filter.
SetMute source
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.
SetOcclusion source
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.
SetPan source
func (v *Voice) SetPan(pan float32)
SetPan moves the voice between -1 (left) and +1 (right).
SetPaused source
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.
SetPitch source
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.
SetRelativeToListener source
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.
SetReverb source
func (v *Voice) SetReverb(send float32)
SetReverb changes the send level into the bus's reverb, or the mixer's.
SetSolo source
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.
SetVelocity source
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.
SetVolume source
func (v *Voice) SetVolume(vol float32)
SetVolume changes the voice's gain; 1 is unity.
type VoiceInfo source
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.
Source files
alloc_test.go audio_test.go bench_test.go binaural.go binaural_test.go bus.go bus_test.go capture.go capture_test.go decode.go denormal_test.go device.go device_test.go effects.go example_test.go flac.go flac_test.go fuzz_test.go golden_test.go hook.go inspect.go milestone5_test.go mixer.go mono_test.go mp3.go music.go music_close_test.go music_loop_test.go music_range_test.go perf_bench_test.go race_test.go record.go record_test.go reverb_test.go scene_test.go sound.go source_spatial.go source_spatial_test.go spatial.go stop_test.go stream.go stream_copy_test.go stream_pitch_test.go stream_rate.go voice_state.go wav.go