audio

package
v0.18.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Index

Constants

View Source
const OutRate = 44100

OutRate is the default device rate; the engine runs at whatever rate it was opened with.

Variables

View Source
var ErrTooManyPending = errors.New("too many pending")

Functions

This section is empty.

Types

type Controller

type Controller struct {
	// contains filtered or unexported fields
}

Controller joins the queue, the library, and the engine: it opens streams, advances on track end, prefetches the next track, and reports skips.

func NewController

func NewController(lib library.Library, eng *Engine, notify func(string)) *Controller

func (*Controller) Clear added in v0.9.1

func (c *Controller) Clear() (requests int)

Clear empties the queue and every request lane and stops playback. It returns how many requested songs were dropped.

func (*Controller) ClearRequests added in v0.17.0

func (c *Controller) ClearRequests() (n int)

ClearRequests drops every lane; it returns the songs dropped.

func (*Controller) Current

func (c *Controller) Current() (library.Track, bool)

func (*Controller) Enqueue

func (c *Controller) Enqueue(ts ...library.Track)

func (*Controller) EnqueueAndPlay added in v0.17.1

func (c *Controller) EnqueueAndPlay(ctx context.Context, ts []library.Track) error

EnqueueAndPlay appends ts and, when nothing is playing, starts the first of them: what someone typing /play into a silent room expects.

func (*Controller) Index

func (c *Controller) Index() (i int)

func (*Controller) Jump

func (c *Controller) Jump(ctx context.Context, i int) error

func (*Controller) Next

func (c *Controller) Next(ctx context.Context) error

func (*Controller) Pending added in v0.17.0

func (c *Controller) Pending() (ls []Lane)

Pending is every lane with its waiting songs, in rotation order.

func (*Controller) PlayTracks

func (c *Controller) PlayTracks(ctx context.Context, ts []library.Track, start int) error

func (*Controller) Prev

func (c *Controller) Prev(ctx context.Context) error

func (*Controller) Remove added in v0.9.1

func (c *Controller) Remove(ctx context.Context, i int) error

Remove drops the track at i from the queue. Removing the playing track moves playback to the one that followed it, or stops when it was last. A prefetched copy of the removed track is discarded so the gapless handover cannot play it.

func (*Controller) Repeat

func (c *Controller) Repeat() (v bool)

func (*Controller) Request added in v0.17.0

func (c *Controller) Request(ctx context.Context, by, name string, ts []library.Track) (int, error)

Request queues songs for someone in the room. With nothing playing it starts the first one at once.

func (*Controller) RequesterOf added in v0.17.0

func (c *Controller) RequesterOf(i int) (by, name string)

RequesterOf is who asked for queue row i; empty for the DJ's own rows.

func (*Controller) Run

func (c *Controller) Run(ctx context.Context)

Run advances the queue when a track ends and prefetches the next track shortly before. It returns when ctx is cancelled.

func (*Controller) SeekBy

func (c *Controller) SeekBy(delta time.Duration)

func (*Controller) SetMaxPending added in v0.17.0

func (c *Controller) SetMaxPending(n int)

SetMaxPending caps the songs one requester may have waiting; 0 = unlimited.

func (*Controller) SetTrackEnd added in v0.17.0

func (c *Controller) SetTrackEnd(fn func(t library.Track, played time.Duration, by, name string))

SetTrackEnd is told every track that stops being current: how much of it was heard and who asked for it.

func (*Controller) Shuffle

func (c *Controller) Shuffle() (v bool)

func (*Controller) TogglePause

func (c *Controller) TogglePause()

func (*Controller) ToggleRepeat

func (c *Controller) ToggleRepeat()

func (*Controller) ToggleShuffle

func (c *Controller) ToggleShuffle()

func (*Controller) Tracks

func (c *Controller) Tracks() (ts []library.Track)

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine runs Sources through decode → resample → ring → output, and taps the output for the analyzer. A second source can be queued with SetNext: the decoder continues straight into it when the first ends, so the ring never empties and the handover is gapless. The reader flips the audible source when it consumes past the boundary, which is when the deck changes.

func NewEngine

func NewEngine(out Output, rate int) *Engine

func (*Engine) Close

func (e *Engine) Close()

func (*Engine) Done

func (e *Engine) Done() <-chan struct{}

func (*Engine) DropNext added in v0.17.0

func (e *Engine) DropNext() bool

DropNext discards the queued next source, but only while the decoder has not read from it; once it has, the ring already holds its start and the source must play. It reports whether nothing is queued afterwards. Unlike TakeNext it never halts the decoder, so the current track plays on.

func (*Engine) Err

func (e *Engine) Err() error

func (*Engine) Feed added in v0.16.0

func (e *Engine) Feed() *Feed

Feed is the output before the volume knob, for one consumer.

func (*Engine) Format added in v0.7.2

func (e *Engine) Format() Format

Format is what the audible source is decoding, or zero when nothing plays.

func (*Engine) Handover

func (e *Engine) Handover() <-chan struct{}

func (*Engine) Length

func (e *Engine) Length() time.Duration

func (*Engine) LocalMuted added in v0.16.0

func (e *Engine) LocalMuted() bool

func (*Engine) Pause

func (e *Engine) Pause()

func (*Engine) PeekNext

func (e *Engine) PeekNext() Source

PeekNext reports the source queued to play next, if any.

func (*Engine) Play

func (e *Engine) Play(src Source)

func (*Engine) PlayAt

func (e *Engine) PlayAt(src Source, at time.Duration)

PlayAt starts src from position at, for resuming a stream that dropped. A source that cannot seek there plays from its start with the clock at 0. Any queued next source other than src is discarded.

func (*Engine) Playing

func (e *Engine) Playing() bool

func (*Engine) Position

func (e *Engine) Position() time.Duration

func (*Engine) Rate added in v0.7.0

func (e *Engine) Rate() int

Rate is the output sample rate the engine was opened with.

func (*Engine) Resume

func (e *Engine) Resume()

func (*Engine) Seek

func (e *Engine) Seek(d time.Duration)

func (*Engine) SetLocalMute added in v0.16.0

func (e *Engine) SetLocalMute(on bool)

SetLocalMute silences the device without touching the volume setting; the feed and the visualizer tap keep flowing.

func (*Engine) SetNext

func (e *Engine) SetNext(src Source)

SetNext queues src to follow the current source without a gap. It replaces any source queued earlier.

func (*Engine) SetVolume

func (e *Engine) SetVolume(v float64)

func (*Engine) Spectrum

func (e *Engine) Spectrum(n int, dst []float64) int

func (*Engine) Stereo added in v0.5.0

func (e *Engine) Stereo(n int, l, r []float64) int

Stereo is Spectrum with left and right kept apart.

func (*Engine) Stop

func (e *Engine) Stop()

func (*Engine) TakeNext

func (e *Engine) TakeNext() Source

TakeNext removes and returns the queued source, rewound if the decoder had already started on it. The current source stops decoding; the caller is expected to Play something next.

func (*Engine) Volume

func (e *Engine) Volume() float64

type Feed added in v0.16.0

type Feed struct {
	// contains filtered or unexported fields
}

Feed hands a copy of the engine's output, before the volume knob, to one consumer such as a Discord broadcast. Writes never block: when the consumer falls behind, the oldest audio is dropped.

func NewFeed added in v0.16.0

func NewFeed(capacityFrames int) *Feed

func (*Feed) Available added in v0.16.0

func (f *Feed) Available() int

Available is how many frames a Read could return now.

func (*Feed) Read added in v0.16.0

func (f *Feed) Read(dst []float32) int

Read copies up to len(dst)/2 frames into dst and returns how many.

func (*Feed) Write added in v0.16.0

func (f *Feed) Write(stereo []float32)

type Format added in v0.7.2

type Format struct {
	Codec      string
	SampleRate int
	BitDepth   int // 0 for lossy codecs
	Bitrate    int // kbps
}

Format is what a decoder knows about the file it is playing. Bitrate is derived from size and length when the container does not state one.

type HTTPFile

type HTTPFile struct {
	// contains filtered or unexported fields
}

HTTPFile is an io.ReadSeekCloser over an HTTP resource that honours Range. A Seek closes the current body; the next Read opens a new request at the new offset. Decoders that seek through the seek table or by byte offset therefore cost one request per seek and nothing while streaming.

func OpenHTTP

func OpenHTTP(ctx context.Context, url string, headers map[string]string) (*HTTPFile, error)

func (*HTTPFile) Close

func (f *HTTPFile) Close() error

func (*HTTPFile) Interrupt

func (f *HTTPFile) Interrupt()

Interrupt closes the in-flight body so a blocked Read returns. The next Read reopens at the current offset; a Seek reopens at the new one.

func (*HTTPFile) Read

func (f *HTTPFile) Read(p []byte) (int, error)

Read streams from the current body. A connection that ends before the known size, or fails outright, is reopened at the current offset up to reconnectAttempts times, so a server that dropped an idle transfer (Plex does, after a long pause) is never seen by the decoder.

func (*HTTPFile) Seek

func (f *HTTPFile) Seek(offset int64, whence int) (int64, error)

func (*HTTPFile) Size

func (f *HTTPFile) Size() int64

type Lane added in v0.17.0

type Lane struct {
	By, Name string
	Tracks   []library.Track
}

Lane is one requester's waiting songs.

type Output

type Output interface {
	NewPlayer(r io.Reader) Player
}

Output creates players that read 32-bit float stereo at the engine's rate from r.

func NewOtoOutput

func NewOtoOutput(rate int) (Output, error)

NewOtoOutput opens the process's single audio context. Call once.

func NewSilentOutput added in v0.8.0

func NewSilentOutput(rate int) Output

NewSilentOutput returns an Output whose players consume audio at the sample rate and discard it. It lets dotamp run where there is no sound device, such as a headless box rendering the README's screenshots, with the visualizers moving exactly as they would over speakers.

type Player

type Player interface {
	Play()
	Pause()
	BufferedSize() int // bytes of float32 stereo still queued in the device
	Close() error
}

Player is the slice of oto.Player the engine needs; tests supply a fake.

type Queue

type Queue struct {
	Shuffle, Repeat bool
	// contains filtered or unexported fields
}

Queue is the ordered list of tracks and a cursor. With Shuffle on, Next walks a random permutation that starts at the current track and is rebuilt when the list changes.

func (*Queue) Append

func (q *Queue) Append(ts ...library.Track)

func (*Queue) Clear

func (q *Queue) Clear()

func (*Queue) Current

func (q *Queue) Current() (library.Track, bool)

func (*Queue) HasNext added in v0.17.0

func (q *Queue) HasNext() bool

HasNext reports whether Next would move on without Repeat wrapping.

func (*Queue) Index

func (q *Queue) Index() int

func (*Queue) InsertNext added in v0.17.0

func (q *Queue) InsertNext(t library.Track, by, name string) int

InsertNext puts t right after the current track, tagged with who asked, and makes it the next track even under Shuffle. It returns the index.

func (*Queue) Jump

func (q *Queue) Jump(i int) bool

func (*Queue) Next

func (q *Queue) Next() bool

func (*Queue) Prev

func (q *Queue) Prev() bool

func (*Queue) Remove added in v0.9.1

func (q *Queue) Remove(i int) (wasCurrent, wasLast bool)

Remove drops the track at i. It reports whether that was the current track and, if so, whether it was the last in the list, in which case the cursor moves back onto the new last track. Otherwise the cursor keeps pointing at the same track, or at the one that followed the removed one.

func (*Queue) Replace

func (q *Queue) Replace(ts []library.Track, start int)

func (*Queue) Tag added in v0.17.0

func (q *Queue) Tag(i int) (by, name string)

Tag is who asked for the track at i; empty for the DJ's own rows.

func (*Queue) Tracks

func (q *Queue) Tracks() []library.Track

type Resampler

type Resampler struct {
	// contains filtered or unexported fields
}

Resampler converts interleaved stereo float32 between sample rates with a windowed-sinc filter: every output sample is a weighted sum of the nearby input samples, the weights being a sinc cut off at the lower of the two Nyquist frequencies under a Kaiser window. Downsampling therefore removes what cannot be represented instead of folding it back as aliasing, and a tone passes through within a fraction of a decibel.

The filter looks sincTaps input frames ahead, so the last sincTaps frames of a stream are only produced once more input arrives; at the end of a track that is about a millisecond, which the next track's first frames cover.

func NewResampler

func NewResampler(inRate, outRate int) *Resampler

func (*Resampler) Delay added in v0.7.0

func (r *Resampler) Delay() float64

Delay is the alignment of output time to input time, in output frames: zero, since the kernel is centred on the exact fractional position.

func (*Resampler) Process

func (r *Resampler) Process(in []float32) []float32

func (*Resampler) Ratio

func (r *Resampler) Ratio() float64

type Ring

type Ring struct {
	// contains filtered or unexported fields
}

Ring is a single-producer single-consumer ring of float32 samples. Only the consumer moves head, so a writer never touches a slot the reader may be copying; a producer-side Drain is a request the consumer applies on its next Read.

func NewRing

func NewRing(capacity int) *Ring

func (*Ring) Drain

func (r *Ring) Drain()

Drain marks everything written so far as stale. The consumer skips it on its next Read; until then Len still counts it, so a full ring stays full.

func (*Ring) Free

func (r *Ring) Free() int

func (*Ring) Head

func (r *Ring) Head() int64

Head and Tail are the monotonic sample positions the consumer has read or skipped up to, and the producer has written up to.

func (*Ring) Len

func (r *Ring) Len() int

func (*Ring) Read

func (r *Ring) Read(p []float32) int

func (*Ring) Tail

func (r *Ring) Tail() int64

func (*Ring) Write

func (r *Ring) Write(p []float32) int

type Source

type Source interface {
	SampleRate() int
	Read(dst []float32) (int, error)
	Seek(d time.Duration) error
	Length() time.Duration
	Format() Format
	// Interrupt makes a Read blocked on I/O return promptly with an error.
	// The source is then only good for Seek or Close.
	Interrupt()
	Close() error
}

Source is a decoded audio stream: interleaved stereo float32 in [-1, 1].

func NewFLAC

func NewFLAC(rs io.ReadSeekCloser) (Source, error)

func NewMP3

func NewMP3(rs io.ReadSeekCloser) (Source, error)

func NewSource

func NewSource(codec string, rs io.ReadSeekCloser) (Source, error)

type Tap

type Tap struct {
	// contains filtered or unexported fields
}

Tap keeps the most recent frames handed to the output so the analyzer can read what the speaker is playing now. Left and right are kept apart; the mono view mixes them on the way out.

func NewTap

func NewTap(capacityFrames int) *Tap

func (*Tap) Latest

func (t *Tap) Latest(n, lagFrames int, dst []float64) int

Latest fills dst with n mono-mixed frames ending lagFrames before the newest. It returns 0 when there is not enough history yet.

func (*Tap) LatestStereo added in v0.5.0

func (t *Tap) LatestStereo(n, lagFrames int, l, r []float64) int

LatestStereo is Latest with the channels kept apart.

func (*Tap) Reset

func (t *Tap) Reset()

func (*Tap) Write

func (t *Tap) Write(p []float32)

func (*Tap) Written

func (t *Tap) Written() int64

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL