Skip to content

Audio

The core/audio package defines a mid-level, bus-based audio API. It is the audio equivalent of the render package: an abstract interface implemented separately for each platform (native desktop, web). Game code is not expected to use this API directly — a higher-level audio API is provided for that purpose — but there are cases where working directly with these primitives is necessary.

The API is obtained from the application window:

api := window.AudioAPI()

Core Concepts

Concept Description
API Entry point. Creates media, buses, and playback instances. Exposes the master bus and spatial listener.
MediaData Raw decoded audio frames together with the sample rate.
Media A decoded audio clip loaded into the audio system. Acts as a data source for playback instances.
Frame A single stereo audio frame consisting of left and right channel values.
MasterBus The overall output sink. Controls master gain and compression.
Bus A named group of sound sources with collective gain, reverb, compression, and pause/resume control.
Playback A single playing instance of a Media on a Bus. Controls start/stop/pause and per-playback properties.
SpatialPlayback A Playback that is also positioned and oriented in 3D space.
SpatialListener The listener's position and orientation in 3D space for spatial audio.

Media

Media represents a decoded audio clip held in the audio system. It is created from a MediaData value, which holds raw PCM frames and a sample rate:

// Decode from an encoded file (WAV, MP3, or any registered format).
data, _, err := audio.Decode(r)
if err != nil {
    // handle error
}

// Resample if necessary (e.g. if the audio system requires a specific rate).
// data.Frames = audio.Resample(data.Frames, data.SampleRate, targetRate)

media := api.CreateMedia(data)

// Release when no longer needed.
defer media.Release()

It is safe to release a Media after using it to create playback instances — existing playback is not affected.

media.Length() returns the duration of the clip in seconds.

Frames and Sample Rate

Audio data is represented as a slice of Frame values, each holding a left and right channel sample:

type Frame struct {
    Left  float32
    Right float32
}

The Resample utility converts frames between sample rates:

resampled := audio.Resample(frames, originalRate, targetRate)

SampleCount and Seconds convert between frame counts and durations:

count := audio.SampleCount(2.5, sampleRate) // frames for 2.5 seconds
dur   := audio.Seconds(count, sampleRate)   // back to seconds

Decoding Audio Files

audio.Decode auto-detects the format by magic-byte prefix and decodes the data:

data, format, err := audio.Decode(r) // format is e.g. "mp3" or "wav"

Format decoders self-register via package init. Import the sub-packages to enable them:

import (
    _ "github.com/mokiat/lacking/core/audio/mp3"
    _ "github.com/mokiat/lacking/core/audio/wav"
)

Custom decoders can be added with audio.RegisterDecoder.

Master Bus

MasterBus is the global output sink. It controls the overall gain and provides access to global compression:

master := api.MasterBus()
master.SetGain(0.8)

comp := master.Compression()
comp.SetThreshold(-18.0)
comp.SetRatio(4.0)

Buses

A Bus groups a set of sound sources for collective control. Create one with CreateBus, optionally enabling reverb and/or compression:

musicBus := api.CreateBus(audio.BusSettings{})
sfxBus   := api.CreateBus(audio.BusSettings{UseCompression: true})
envBus   := api.CreateBus(audio.BusSettings{UseReverb: true, UseCompression: true})
defer musicBus.Release()
defer sfxBus.Release()
defer envBus.Release()

Releasing a bus stops all playback attached to it.

Bus Controls

bus.SetGain(0.5)  // half volume for everything on this bus

// Pause/resume all sources on the bus at once.
bus.Pause()
bus.Resume()

bus.Compression() and bus.Reverb() return nil if the bus was not created with those effects enabled.

Reverb

Configure room characteristics on buses created with UseReverb: true:

Parameter Default Range Description
RoomSize 0.3 [0.0, 1.0] Size of the virtual room.
Damping 0.5 [0.0, 1.0] High-frequency absorption. Higher values simulate softer surfaces.
Dry 1.0 [0.0, 1.0] Level of the unprocessed signal.
Wet 0.5 [0.0, 1.0] Level of the reverberated signal.
reverb := bus.Reverb()
reverb.SetRoomSize(0.8)
reverb.SetDamping(0.3)
reverb.SetWet(0.4)

Compression

Available on both Bus (when UseCompression: true) and MasterBus:

Parameter Default Range Description
Threshold -24.0 dB [-100.0, 0.0] Level above which compression is applied.
Ratio 12.0 [1.0, 20.0] Compression ratio (input dB : output dB above threshold).
Knee 30.0 dB [0.0, 40.0] Width of the soft-knee transition around the threshold.
Attack 0.003 s [0.0, 1.0] Time for compression to engage.
Release 0.25 s [0.0, 1.0] Time for compression to disengage.
comp := bus.Compression()
comp.SetThreshold(-18.0)
comp.SetRatio(4.0)

Playback

A Playback is a single instance of a Media playing on a Bus. Create one with CreatePlayback:

playback := api.CreatePlayback(bus, media, audio.PlaybackSettings{})
defer playback.Release()

playback.Start(0)    // start from the beginning
playback.Pause()     // pause; position is preserved
playback.Resume()    // resume from paused position
playback.Stop()      // stop and reset position

playback.Playing() reports whether the playback is currently active.

Loop Control

playback.SetLooping(true)
playback.SetLoopStart(1.0) // loop from 1.0 s
playback.SetLoopEnd(4.5)   // to 4.5 s

Per-Playback Controls

playback.SetGain(0.7)          // individual volume
playback.SetPlaybackRate(1.5)  // 1.5× speed; pitch shifts accordingly

DBToGain and GainToDB convert between decibels and linear gain:

playback.SetGain(audio.DBToGain(-6.0)) // -6 dB

Completion Callback

playback.SetOnFinished(func() {
    // called when the media plays through naturally (not on Stop or Pause,
    // and not on each loop iteration)
})

Per-Playback Filters

Low-pass and high-pass filters can be enabled at creation time:

playback := api.CreatePlayback(bus, media, audio.PlaybackSettings{
    UseLowPassFilter:  true,
    UseHighPassFilter: true,
})

playback.LowPassFilter().SetFrequency(8000.0)  // remove hiss above 8 kHz
playback.HighPassFilter().SetFrequency(80.0)   // remove rumble below 80 Hz

LowPassFilter() and HighPassFilter() return nil if the respective filter was not enabled at creation.

Spatial Audio

CreateSpatialPlayback returns a SpatialPlayback, which combines Playback with SpatialEmitter. The sound source is positioned in 3D space and attenuated relative to the SpatialListener.

// Update the listener each frame to match the camera.
listener := api.SpatialListener()
listener.SetPosition(cameraPosition)
listener.SetRotation(cameraRotation)

// Create a positioned sound source.
spatial := api.CreateSpatialPlayback(bus, media, audio.PlaybackSettings{})
defer spatial.Release()

spatial.SetPosition(sprec.Vec3{X: 10, Y: 0, Z: -5})
spatial.Start(0)

Directional Emission (Cone)

By default a spatial source emits equally in all directions (OuterConeAngle = 360°). Narrowing the cone makes the source directional:

spatial.SetInnerConeAngle(sprec.Degrees(30)) // full gain within 30°
spatial.SetOuterConeAngle(sprec.Degrees(90)) // fade to outer gain by 90°
spatial.SetOuterConeGain(0.0)                // silent outside the outer cone
Property Default Description
InnerConeAngle Within this angle the emitter plays at full gain.
OuterConeAngle 360° Beyond this angle the gain is OuterConeGain. Between inner and outer the gain is linearly interpolated.
OuterConeGain 0.0 Gain applied when the listener is outside the outer cone.

No-op Implementation

NewNopAPI returns a fully functional but silent implementation. All methods work correctly and return valid objects; no audio is produced. Useful for headless environments and tests:

api := audio.NewNopAPI()