182 lines
5.5 KiB
Go
182 lines
5.5 KiB
Go
|
// Copyright 2021 The Oto Authors
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package oto
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/ebitengine/oto/v3/internal/mux"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
contextCreated bool
|
||
|
contextCreationMutex sync.Mutex
|
||
|
)
|
||
|
|
||
|
// Context is the main object in Oto. It interacts with the audio drivers.
|
||
|
//
|
||
|
// To play sound with Oto, first create a context. Then use the context to create
|
||
|
// an arbitrary number of players. Then use the players to play sound.
|
||
|
//
|
||
|
// Creating multiple contexts is NOT supported.
|
||
|
type Context struct {
|
||
|
context *context
|
||
|
}
|
||
|
|
||
|
// Format is the format of sources.
|
||
|
type Format int
|
||
|
|
||
|
const (
|
||
|
// FormatFloat32LE is the format of 32 bits floats little endian.
|
||
|
FormatFloat32LE Format = iota
|
||
|
|
||
|
// FormatUnsignedInt8 is the format of 8 bits integers.
|
||
|
FormatUnsignedInt8
|
||
|
|
||
|
//FormatSignedInt16LE is the format of 16 bits integers little endian.
|
||
|
FormatSignedInt16LE
|
||
|
)
|
||
|
|
||
|
// NewContextOptions represents options for NewContext.
|
||
|
type NewContextOptions struct {
|
||
|
// SampleRate specifies the number of samples that should be played during one second.
|
||
|
// Usual numbers are 44100 or 48000. One context has only one sample rate. You cannot play multiple audio
|
||
|
// sources with different sample rates at the same time.
|
||
|
SampleRate int
|
||
|
|
||
|
// ChannelCount specifies the number of channels. One channel is mono playback. Two
|
||
|
// channels are stereo playback. No other values are supported.
|
||
|
ChannelCount int
|
||
|
|
||
|
// Format specifies the format of sources.
|
||
|
Format Format
|
||
|
|
||
|
// BufferSize specifies a buffer size in the underlying device.
|
||
|
//
|
||
|
// If 0 is specified, the driver's default buffer size is used.
|
||
|
// Set BufferSize to adjust the buffer size if you want to adjust latency or reduce noises.
|
||
|
// Too big buffer size can increase the latency time.
|
||
|
// On the other hand, too small buffer size can cause glitch noises due to buffer shortage.
|
||
|
BufferSize time.Duration
|
||
|
}
|
||
|
|
||
|
// NewContext creates a new context with given options.
|
||
|
// A context creates and holds ready-to-use Player objects.
|
||
|
// NewContext returns a context, a channel that is closed when the context is ready, and an error if it exists.
|
||
|
//
|
||
|
// Creating multiple contexts is NOT supported.
|
||
|
func NewContext(options *NewContextOptions) (*Context, chan struct{}, error) {
|
||
|
contextCreationMutex.Lock()
|
||
|
defer contextCreationMutex.Unlock()
|
||
|
|
||
|
if contextCreated {
|
||
|
return nil, nil, fmt.Errorf("oto: context is already created")
|
||
|
}
|
||
|
contextCreated = true
|
||
|
|
||
|
var bufferSizeInBytes int
|
||
|
if options.BufferSize != 0 {
|
||
|
// The underying driver always uses 32bit floats.
|
||
|
bytesPerSample := options.ChannelCount * 4
|
||
|
bytesPerSecond := options.SampleRate * bytesPerSample
|
||
|
bufferSizeInBytes = int(int64(options.BufferSize) * int64(bytesPerSecond) / int64(time.Second))
|
||
|
bufferSizeInBytes = bufferSizeInBytes / bytesPerSample * bytesPerSample
|
||
|
}
|
||
|
ctx, ready, err := newContext(options.SampleRate, options.ChannelCount, mux.Format(options.Format), bufferSizeInBytes)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
return &Context{context: ctx}, ready, nil
|
||
|
}
|
||
|
|
||
|
// NewPlayer creates a new, ready-to-use Player belonging to the Context.
|
||
|
// It is safe to create multiple players.
|
||
|
//
|
||
|
// The format of r is as follows:
|
||
|
//
|
||
|
// [data] = [sample 1] [sample 2] [sample 3] ...
|
||
|
// [sample *] = [channel 1] [channel 2] ...
|
||
|
// [channel *] = [byte 1] [byte 2] ...
|
||
|
//
|
||
|
// Byte ordering is little endian.
|
||
|
//
|
||
|
// A player has some amount of an underlying buffer.
|
||
|
// Read data from r is queued to the player's underlying buffer.
|
||
|
// The underlying buffer is consumed by its playing.
|
||
|
// Then, r's position and the current playing position don't necessarily match.
|
||
|
// If you want to clear the underlying buffer for some reasons e.g., you want to seek the position of r,
|
||
|
// call the player's Reset function.
|
||
|
//
|
||
|
// You cannot share r by multiple players.
|
||
|
//
|
||
|
// The returned player implements Player, BufferSizeSetter, and io.Seeker.
|
||
|
// You can modify the buffer size of a player by the SetBufferSize function.
|
||
|
// A small buffer size is useful if you want to play a real-time PCM for example.
|
||
|
// Note that the audio quality might be affected if you modify the buffer size.
|
||
|
//
|
||
|
// If r does not implement io.Seeker, the returned player's Seek returns an error.
|
||
|
//
|
||
|
// NewPlayer is concurrent-safe.
|
||
|
//
|
||
|
// All the functions of a Player returned by NewPlayer are concurrent-safe.
|
||
|
func (c *Context) NewPlayer(r io.Reader) *Player {
|
||
|
return &Player{
|
||
|
player: c.context.mux.NewPlayer(r),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Suspend suspends the entire audio play.
|
||
|
//
|
||
|
// Suspend is concurrent-safe.
|
||
|
func (c *Context) Suspend() error {
|
||
|
return c.context.Suspend()
|
||
|
}
|
||
|
|
||
|
// Resume resumes the entire audio play, which was suspended by Suspend.
|
||
|
//
|
||
|
// Resume is concurrent-safe.
|
||
|
func (c *Context) Resume() error {
|
||
|
return c.context.Resume()
|
||
|
}
|
||
|
|
||
|
// Err returns the current error.
|
||
|
//
|
||
|
// Err is concurrent-safe.
|
||
|
func (c *Context) Err() error {
|
||
|
return c.context.Err()
|
||
|
}
|
||
|
|
||
|
type atomicError struct {
|
||
|
err error
|
||
|
m sync.Mutex
|
||
|
}
|
||
|
|
||
|
func (a *atomicError) TryStore(err error) {
|
||
|
a.m.Lock()
|
||
|
defer a.m.Unlock()
|
||
|
if a.err == nil {
|
||
|
a.err = err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (a *atomicError) Load() error {
|
||
|
a.m.Lock()
|
||
|
defer a.m.Unlock()
|
||
|
return a.err
|
||
|
}
|