You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cointop/vendor/github.com/hajimehoshi/oto/context.go

165 lines
4.5 KiB
Go

// Copyright 2019 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 (
"errors"
"io"
"sync"
"time"
"github.com/hajimehoshi/oto/internal/mux"
)
type Context struct {
driverWriter *driverWriter
mux *mux.Mux
errCh chan error
}
var (
theContext *Context
contextM sync.Mutex
)
var errClosed = errors.New("closed")
// NewContext creates a new context, that creates and holds ready-to-use Player objects.
//
// The sampleRate argument specifies the number of samples that should be played during one second.
// Usual numbers are 44100 or 48000.
//
// The channelNum argument specifies the number of channels. One channel is mono playback. Two
// channels are stereo playback. No other values are supported.
//
// The bitDepthInBytes argument specifies the number of bytes per sample per channel. The usual value
// is 2. Only values 1 and 2 are supported.
//
// The bufferSizeInBytes argument specifies the size of the buffer of the Context. This means, how
// many bytes can Context remember before actually playing them. Bigger buffer can reduce the number
// of Player's Write calls, thus reducing CPU time. Smaller buffer enables more precise timing. The
// longest delay between when samples were written and when they started playing is equal to the size
// of the buffer.
func NewContext(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*Context, error) {
contextM.Lock()
defer contextM.Unlock()
if theContext != nil {
panic("oto: NewContext can be called only once")
}
d, err := newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes)
if err != nil {
return nil, err
}
dw := &driverWriter{
driver: d,
bufferSize: bufferSizeInBytes,
bytesPerSecond: sampleRate * channelNum * bitDepthInBytes,
}
c := &Context{
driverWriter: dw,
mux: mux.New(channelNum, bitDepthInBytes),
errCh: make(chan error),
}
theContext = c
go func() {
if _, err := io.Copy(c.driverWriter, c.mux); err != nil {
c.errCh <- err
}
close(c.errCh)
}()
return c, nil
}
// NewPlayer is a short-hand of creating a Context by NewContext and a Player by the context's NewPlayer.
func NewPlayer(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*Player, error) {
c, err := NewContext(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes)
if err != nil {
return nil, err
}
return c.NewPlayer(), nil
}
// NewPlayer creates a new, ready-to-use Player belonging to the Context.
func (c *Context) NewPlayer() *Player {
p := newPlayer(c)
c.mux.AddSource(p.r)
return p
}
// Close closes the Context and its Players and frees any resources associated with it. The Context is no longer
// usable after calling Close.
func (c *Context) Close() error {
contextM.Lock()
theContext = nil
contextM.Unlock()
if err := c.driverWriter.Close(); err != nil {
return err
}
if err := c.mux.Close(); err != nil {
return err
}
return <-c.errCh
}
type driverWriter struct {
driver *driver
bufferSize int
bytesPerSecond int
m sync.Mutex
}
func (d *driverWriter) Write(buf []byte) (int, error) {
d.m.Lock()
defer d.m.Unlock()
written := 0
for len(buf) > 0 {
if d.driver == nil {
return written, errClosed
}
n, err := d.driver.TryWrite(buf)
written += n
if err != nil {
return written, err
}
buf = buf[n:]
// When not all buf is written, the underlying buffer is full.
// Mitigate the busy loop by sleeping (#10).
if len(buf) > 0 {
t := time.Second * time.Duration(d.bufferSize) / time.Duration(d.bytesPerSecond) / 8
time.Sleep(t)
}
}
return written, nil
}
func (d *driverWriter) Close() error {
d.m.Lock()
defer d.m.Unlock()
// Close should be wait until the buffer data is consumed (#36).
// This is the simplest (but ugly) fix.
// TODO: Implement player's Close to wait the buffer played.
time.Sleep(time.Second * time.Duration(d.bufferSize) / time.Duration(d.bytesPerSecond))
if err := d.driver.Close(); err != nil {
return err
}
return nil
}