Documentation
¶
Overview ¶
Package beepout implements playback of audio streamers through the malgo (miniaudio) backend.
beepout is structurally compatible with both github.com/gopxl/beep and github.com/gopxl/beep/v2: it accepts any value whose Stream and Err methods match the well-known beep.Streamer signature, without importing either beep module from the library implementation. See the Streamer interface in this file for the exact contract.
Example (SineWave) ¶
Example_sineWave generates a 440 Hz sine tone using beepout's own StreamerFunc type and plays it through the default backend. It does not depend on beep at all, demonstrating that the beepout API is usable standalone.
const sampleRate = 44100
const freq = 440.0
s, err := New(Config{
SampleRate: sampleRate,
BufferSize: 2048,
Volume: 0.5,
})
if err != nil {
panic(err)
}
defer s.Close()
// A self-contained infinite sine generator. The phase is tracked in
// the closure and wrapped with math.Modf so it stays in [0, 1).
dt := freq / float64(sampleRate)
var t float64
tone := StreamerFunc(func(samples [][2]float64) (int, bool) {
for i := range samples {
v := math.Sin(t * 2.0 * math.Pi)
samples[i][0] = v
samples[i][1] = v
_, t = math.Modf(t + dt)
}
return len(samples), true
})
s.Play(tone)
<-time.After(2 * time.Second)
s.Clear()
Index ¶
- func Backends() []malgo.Backend
- func BufferSize() int
- func Clear()
- func Close()
- func Init(sampleRate int, bufferSize int, opts ...InitOption) error
- func Lock()
- func Play(streamers ...Streamer)
- func PlayAndWait(streamers ...Streamer)
- func Reinit(cfg Config) error
- func Resume() error
- func SampleRate() int
- func SetBackends(backends []malgo.Backend) error
- func Suspend() error
- func Unlock()
- type Config
- type InitOption
- type Speaker
- func (s *Speaker) Backends() []malgo.Backend
- func (s *Speaker) BufferSize() int
- func (s *Speaker) Clear()
- func (s *Speaker) Close() error
- func (s *Speaker) Lock()
- func (s *Speaker) Pause()
- func (s *Speaker) Play(streamers ...Streamer)
- func (s *Speaker) Resume()
- func (s *Speaker) SampleRate() int
- func (s *Speaker) Unlock()
- type Streamer
- type StreamerFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Backends ¶
Backends returns the malgo backend priority list that the default Speaker was initialized with (via Init or Reinit). The returned slice is a fresh copy and may be safely modified by the caller.
Returns nil if no default Speaker is currently initialized, or if the default Speaker was initialized via Init (which uses malgo's platform default ordering, represented internally as nil).
Backends is beepout-specific: it has no equivalent in the beep v1/v2 speaker API.
func BufferSize ¶
func BufferSize() int
BufferSize returns the device period size in frames that the default Speaker was initialized with. Returns 0 if no default Speaker is currently initialized.
BufferSize is beepout-specific: the beep v1/v2 speaker package does not expose a package-level BufferSize function.
func Clear ¶
func Clear()
Clear removes all queued streamers from the default Speaker. Samples already buffered by the device may still be played.
Clear must not be called while the caller holds the speaker lock acquired via Lock — that would deadlock.
func Close ¶
func Close()
Close closes the package-level default Speaker and releases all of its resources. It is safe to call Close multiple times; subsequent calls are no-ops. After Close returns, the default Speaker must be reinitialized with Init before any other package-level function can be used again.
Close does not return an error, matching the beep v1 and v2 speaker.Close() signature. If you need the close error, use a *Speaker created with New and call its Close method directly.
func Init ¶
func Init(sampleRate int, bufferSize int, opts ...InitOption) error
Init initializes the package-level default Speaker with the given sample rate and buffer size, plus optional configuration applied via InitOption values (such as WithBackends, WithVolume, WithLogProc).
The original two-argument form is preserved for backwards compatibility:
beepout.Init(48000, 2048)
passes nil backends (malgo's platform default) and a neutral volume of 1.0. The variadic form allows additional configuration without requiring callers to construct a full Config:
beepout.Init(48000, 2048,
beepout.WithBackends(malgo.BackendAaudio, malgo.BackendOpensl),
beepout.WithVolume(0.8))
Init must be called once before any other package-level function (other than Close) can be used. Init returns an error if the default Speaker has already been initialized; call Close first to reinitialize.
sampleRate is in frames per second (e.g. 44100, 48000). bufferSize is the device period size in frames (e.g. 256, 2048); larger values reduce CPU usage, smaller values reduce latency.
On failure, Init does not modify the default Speaker state: if a default Speaker was already initialized, it remains in place; if no default Speaker was initialized, it remains nil.
func Lock ¶
func Lock()
Lock acquires the default Speaker's mutex. While held, the audio callback will not advance any queued streamer, making it safe to mutate the state of a streamer that has already been added via Play.
Lock must be paired with Unlock. Hold the lock for as short a time as possible — the audio callback blocks while the lock is held, and holding it for longer than one device period will cause an underrun.
Do not call Play, PlayAndWait, Clear, Suspend, Resume, Init, or Close while holding the lock: those functions acquire the same mutex internally and would deadlock. The intended pattern is to Lock, mutate streamer state directly, then Unlock.
Lock must only be called after Init has succeeded; calling Lock before Init is a programming error and Unlock will panic when it tries to release a mutex that was never acquired.
func Play ¶
func Play(streamers ...Streamer)
Play adds the given streamers to the default Speaker's playback queue. Streamers are mixed additively; drained streamers are removed automatically by the audio callback.
Play must not be called while the caller already holds the speaker lock acquired via Lock — that would deadlock. See Lock for details.
func PlayAndWait ¶
func PlayAndWait(streamers ...Streamer)
PlayAndWait plays the given streamers through the default Speaker and blocks until all of them have drained. It then sleeps for the duration of one buffer period to allow any samples already in the device buffer to be played, mirroring the behavior of beep v1/v2 speaker.PlayAndWait.
Like Play, PlayAndWait must not be called while the caller holds the speaker lock.
Each streamer is wrapped internally so that its drain signal is observable from the calling goroutine. The wrapper satisfies beepout.Streamer (and therefore beep.Streamer from both v1 and v2) without altering the original streamer's samples or error.
func Reinit ¶
Reinit re-initializes the default Speaker with the given Config, replacing the previous one entirely.
Reinit is more flexible than SetBackends: it allows changing the sample rate, buffer size, volume, backend priority list, and log callback in a single call. Use SetBackends if only the backend priority list needs to change, or use the variadic Init with options (WithBackends, WithVolume, WithLogProc) for the initial configuration.
Reinit is atomic with respect to failure: the new Speaker is constructed WITHOUT closing the current one. If the new Speaker cannot be created, the current default Speaker is left untouched and Reinit returns an error. Callers can rely on the default Speaker remaining usable after a failed Reinit.
On success, the old default Speaker is closed and replaced atomically: no callers observe a moment with no default Speaker. Any streamers that were queued via Play are lost when the old Speaker is closed; callers should re-add them after Reinit returns successfully.
Reinit returns an error if no default Speaker has been initialized (call Init first) or if the new Speaker cannot be created.
Reinit must not be called concurrently with Init, Close, or another SetBackends/Reinit; lifecycle operations on the default Speaker are not serialized against each other.
Reinit is beepout-specific: it has no equivalent in the beep v1/v2 speaker API.
func Resume ¶
func Resume() error
Resume restarts the default Speaker's playback device after a prior Suspend. If the device was not suspended, Resume is a no-op.
Resume is independent of the Speaker.Resume method, which undoes a Speaker.Pause call. The two address different layers: Speaker.Pause and Speaker.Resume toggle a flag in the mixer; Suspend and Resume toggle the device's start/stop state.
Resume must not be called while the caller holds the speaker lock acquired via Lock — that would deadlock.
func SampleRate ¶
func SampleRate() int
SampleRate returns the playback sample rate in frames per second that the default Speaker was initialized with. Returns 0 if no default Speaker is currently initialized.
SampleRate is beepout-specific: the beep v1/v2 speaker package does not expose a package-level SampleRate function. It is provided here for symmetry with the (*Speaker).SampleRate method.
func SetBackends ¶
SetBackends re-initializes the default Speaker with the given malgo backend priority list, preserving the sample rate, buffer size, and volume that were configured by the previous Init or Reinit call.
SetBackends is atomic with respect to failure: the new Speaker is constructed WITHOUT closing the current one. If the new Speaker cannot be created (for example because none of the requested backends are available on this platform), the current default Speaker is left untouched and SetBackends returns an error. Callers can rely on the default Speaker remaining usable after a failed SetBackends.
On success, the old default Speaker is closed and replaced atomically: no callers observe a moment with no default Speaker. Any streamers that were queued via Play are lost when the old Speaker is closed; callers should re-add them after SetBackends returns successfully.
SetBackends returns an error if no default Speaker has been initialized (call Init first) or if the new Speaker cannot be created.
SetBackends must not be called concurrently with Init, Close, or another SetBackends/Reinit; lifecycle operations on the default Speaker are not serialized against each other.
SetBackends is beepout-specific: it has no equivalent in the beep v1/v2 speaker API, which only allows backends to be configured at Init time.
func Suspend ¶
func Suspend() error
Suspend suspends audio playback on the default Speaker by stopping the underlying malgo device. The device is not uninitialized; use Resume to restart it.
Suspend is independent of the Speaker.Pause method, which uses a flag-based approach to silence the output without stopping the device. Suspend saves more power but has higher resume latency than Pause, and is intended for situations where no audio will be played for an extended period.
Suspend must not be called while the caller holds the speaker lock acquired via Lock — that would deadlock. Suspend also must not run concurrently with Close on the default speaker; the two operations both touch the underlying malgo device and are not synchronized against each other (matching the same constraint in beep v1/v2's speaker package).
func Unlock ¶
func Unlock()
Unlock releases the default Speaker's mutex previously acquired by Lock.
Unlock must only be called after a successful Lock. Calling Unlock without a prior Lock (or after the default Speaker has been reinitialized by Init between Lock and Unlock) is a programming error and may panic.
Types ¶
type Config ¶
type Config struct {
// SampleRate is the playback sample rate in frames per second. Must be
// positive. Typical values are 44100, 48000, etc.
SampleRate int
// BufferSize is the device period size in frames. Larger values reduce
// CPU usage and the risk of underruns; smaller values reduce latency.
// Must be positive. Common values range from 256 to 4096.
BufferSize int
// Backends is an ordered list of preferred malgo backends. The first
// backend that initializes successfully is used. If empty, malgo's
// platform default backend order applies.
//
// On Android, callers should pass:
//
// []malgo.Backend{malgo.BackendAaudio, malgo.BackendOpensl}
//
// so that AAudio is preferred (lower latency, better features) and
// OpenSL ES is used as a fallback on older devices.
Backends []malgo.Backend
// Volume is a linear gain applied to every mixed sample before it is
// written to the device. 1.0 leaves the signal unchanged, 0.0 mutes
// playback, 0.5 halves the amplitude. The zero value defaults to 1.0
// at construction time, so callers that want true silence must set
// Volume explicitly to a very small positive number.
Volume float64
// LogProc, if non-nil, is invoked by malgo with diagnostic messages.
// It may be called from any goroutine; callers are responsible for any
// synchronization required by their logging implementation.
LogProc malgo.LogProc
}
Config configures a Speaker at construction time. The zero value is not usable; SampleRate and BufferSize must be set explicitly.
type InitOption ¶
type InitOption func(*initConfig)
InitOption is an optional configuration argument for the package-level Init function. An InitOption mutates an internal config builder; it is applied left-to-right, with later options overriding earlier ones for the same field.
InitOption values are constructed by the WithBackends, WithVolume, and WithLogProc helper functions. Callers may also define their own InitOption values for use cases such as conditional configuration based on host platform detection.
InitOption is beepout-specific: the beep v1/v2 speaker package has no options API because it does not expose backend configuration at all.
func WithBackends ¶
func WithBackends(backends ...malgo.Backend) InitOption
WithBackends specifies the malgo backend priority list for the default Speaker. malgo tries each backend in order and uses the first one that initializes successfully on the host platform.
If WithBackends is not passed to Init (or is passed with no arguments), Init uses malgo's platform default backend ordering.
On Android, callers should pass:
beepout.Init(48000, 2048,
beepout.WithBackends(malgo.BackendAaudio, malgo.BackendOpensl))
so that AAudio is preferred (lower latency, better features) and OpenSL|ES is used as a fallback on older devices.
WithBackends copies the caller's slice; external mutation of the argument after WithBackends returns does not affect the configured priority list.
WithBackends is beepout-specific.
func WithLogProc ¶
func WithLogProc(logProc malgo.LogProc) InitOption
WithLogProc specifies an optional malgo logging callback. malgo invokes the callback from arbitrary goroutines; callers are responsible for any synchronization required by their logging implementation.
If WithLogProc is not passed to Init, no logging callback is registered.
WithLogProc is beepout-specific.
func WithVolume ¶
func WithVolume(volume float64) InitOption
WithVolume specifies the linear gain applied to every mixed sample before it is written to the device. 1.0 leaves the signal unchanged, 0.5 halves the amplitude, 0.0 is treated as the default (1.0) for Config-compatibility — callers that want true digital silence should pass a very small positive number such as 1e-10.
If WithVolume is not passed to Init, the default Speaker uses a neutral volume of 1.0.
WithVolume is beepout-specific.
type Speaker ¶
type Speaker struct {
// contains filtered or unexported fields
}
Speaker is a malgo-backed audio sink that plays Streamer values. A Speaker owns a malgo context and a single playback device. Speaker methods are safe to call from multiple goroutines concurrently.
After Close returns, the Speaker is no longer usable. All public methods become no-ops (Close remains safe to call any number of times).
func New ¶
New creates and starts a Speaker.
New initializes a malgo context using the configured backend priority, creates a stereo Float32 playback device with the requested sample rate and period size, and starts the device automatically. The returned Speaker is ready to accept streamers via Play.
If New returns an error, no resources are leaked: any partially initialized malgo state is cleaned up before returning.
func (*Speaker) Backends ¶
Backends returns the malgo backend priority list that the Speaker was constructed with (a copy of Config.Backends at construction time). The returned slice is a fresh copy and may be safely modified by the caller.
If Config.Backends was nil (the platform default) at construction time, Backends returns nil.
func (*Speaker) BufferSize ¶
BufferSize returns the device period size in frames that the Speaker was constructed with. The value is taken from Config.BufferSize at construction time and does not change for the lifetime of the Speaker.
func (*Speaker) Clear ¶
func (s *Speaker) Clear()
Clear removes all queued streamers. Samples already buffered by the device may still be played.
func (*Speaker) Close ¶
Close stops the playback device, uninitializes it, uninitializes the malgo context, and frees all associated resources.
Close is idempotent: calling it multiple times is safe and returns the error from the first invocation on every subsequent call.
After Close returns, every other Speaker method becomes a no-op.
func (*Speaker) Lock ¶
func (s *Speaker) Lock()
Lock acquires the speaker mutex. While held, the audio callback will not advance any queued streamer, which makes it safe to mutate the state of a streamer that has already been added via Play.
Lock must be paired with Unlock. Hold the lock for as short a time as possible — the audio callback blocks while the lock is held, and holding it for longer than one device period will cause an underrun.
Do not call Play, Clear, Pause, Resume, or Close while holding the lock: those methods acquire the same mutex internally and would deadlock. The intended pattern is to Lock, mutate streamer state directly, then Unlock.
func (*Speaker) Pause ¶
func (s *Speaker) Pause()
Pause stops emitting audio from queued streamers without stopping the device. While paused, the audio callback outputs silence and queued streamers are not advanced, so playback resumes from the same position.
Pause is independent from malgo's device start/stop state; the device keeps running so that Resume can take effect with the lowest possible latency. To save power when no audio is playing for an extended period, consider calling Close instead.
func (*Speaker) Play ¶
Play adds the given streamers to the playback queue. Streamers are mixed together additively. Drained streamers are removed from the queue automatically by the audio callback.
Play is safe to call from multiple goroutines, but must not be called while the caller already holds the speaker lock acquired via Lock — that would deadlock. See Lock for details.
func (*Speaker) Resume ¶
func (s *Speaker) Resume()
Resume undoes a prior Pause. If the speaker was not paused, Resume is a no-op.
func (*Speaker) SampleRate ¶
SampleRate returns the playback sample rate in frames per second. The value is taken from Config.SampleRate at construction time and does not change for the lifetime of the Speaker.
type Streamer ¶
Streamer is the minimal contract required by beepout for an audio source.
The signature is identical to the Streamer interface exposed by both github.com/gopxl/beep and github.com/gopxl/beep/v2, so values of type beep.Streamer from either version are accepted transparently by Play and any other beepout API that takes a Streamer. Future beep releases that preserve this signature will continue to work without code changes.
Stream copies at most len(samples) next stereo frames into samples. Each frame is a [2]float64 where index 0 is the left channel and index 1 is the right channel. Sample values are linear PCM in the range [-1, 1].
Stream returns the number of frames streamed and a boolean ok that reports whether more samples may follow. The three valid return patterns are:
- n == len(samples) && ok — the streamer produced all requested frames.
- 0 < n && n < len(samples) && ok — the streamer is now drained; only case 3 may follow.
- n == 0 && !ok — the streamer is exhausted permanently.
Err returns a non-nil error if streaming failed. When Err returns a non-nil error, Stream must return 0, false forever afterwards.
type StreamerFunc ¶
StreamerFunc adapts an ordinary function into a Streamer. It is the structural twin of beep.StreamerFunc from both beep v1 and v2, so a value of type beep.StreamerFunc satisfies beepout.Streamer directly.
Example:
tone := beepout.StreamerFunc(func(samples [][2]float64) (int, bool) {
for i := range samples {
samples[i] = [2]float64{0.5, 0.5}
}
return len(samples), true
})
speaker.Play(tone)
func (StreamerFunc) Err ¶
func (sf StreamerFunc) Err() error
Err always returns nil. StreamerFunc has no error state.