reimplement speaker
This commit is contained in:
parent
cc1e4c9381
commit
8d9485af7e
|
@ -1,139 +0,0 @@
|
|||
package playback
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/faiface/pixel/audio"
|
||||
"github.com/hajimehoshi/oto"
|
||||
)
|
||||
|
||||
// Speaker is the interface used for playing back audio.Streamers.
|
||||
type Speaker interface {
|
||||
// Play tells the Speaker that it is ready for playback and handles preparing the Streamer.
|
||||
Play(audio.Streamer)
|
||||
// Update is called once per game loop and handles pulling samples from the Streamer and writing them to Speaker's
|
||||
// player.
|
||||
Update() error
|
||||
}
|
||||
|
||||
// DefaultSpeaker is a default implementation of speaker capable of playing back samples to the default output device.
|
||||
type DefaultSpeaker struct {
|
||||
// audio.Streamer is the Streamer to pull samples from. It is passed in and set with Speaker.Play(audio.Streamer)
|
||||
audio.Streamer
|
||||
// isPlaying informs the update loop about whether or not this Speaker is playing
|
||||
isPlaying bool
|
||||
// samples is the internal buffer of samples that read() and readSample() fill and drain, respectively
|
||||
// samples' length is the total buffer size / 2
|
||||
samples [][2]float64
|
||||
// player is the underlying *oto.Player, which uses os specific APIs for audio playback
|
||||
player *oto.Player
|
||||
// buf is the buffer of samples converted to bytes that is written to player
|
||||
buf []uint8
|
||||
// bufferSize is the size in bytes of the total buffer in bytes. bufferSize must be a power of 2.
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
// NewDefaultSpeaker returns a *DefaultSpeaker ready to read samples and write to the underlying player for playback
|
||||
func NewDefaultSpeaker(bufferSize int) (*DefaultSpeaker, error) {
|
||||
p, err := oto.NewPlayer(int(audio.SampleRate), 2, 2, bufferSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DefaultSpeaker{
|
||||
player: p,
|
||||
samples: make([][2]float64, bufferSize/2),
|
||||
buf: make([]uint8, bufferSize),
|
||||
bufferSize: bufferSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrBufferMustBePowerOf2 should be returned when the buffer passed in to read is not a power of 2, as a sample is
|
||||
// 2 bytes, the buffer must have the capacity to handle all samples.
|
||||
ErrBufferMustBePowerOf2 = errors.New("Buffer passed to Read must be a power of 2")
|
||||
)
|
||||
|
||||
// read reads up to len(dst) / 2 samples into dst
|
||||
func (s *DefaultSpeaker) read(dst []byte) (n int, err error) {
|
||||
if !s.isPlaying || s.eof() {
|
||||
return 0, io.EOF
|
||||
}
|
||||
// we need dst to be a power of two in order for us to write samples cleanly
|
||||
if len(dst)%2 != 0 {
|
||||
return 0, ErrBufferMustBePowerOf2
|
||||
}
|
||||
|
||||
if l := len(dst); l > 1 {
|
||||
for n < l-1 {
|
||||
sample := s.readSample()
|
||||
dst[n] = byte(sample[0])
|
||||
dst[n+1] = byte(sample[1])
|
||||
if s.eof() {
|
||||
s.samples = make([][2]float64, s.bufferSize/2)
|
||||
break
|
||||
}
|
||||
n += 2
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// eof returns whether or not we have read all samples currently in the samples buffer
|
||||
func (s *DefaultSpeaker) eof() bool {
|
||||
return len(s.samples) == 0
|
||||
}
|
||||
|
||||
// Sample is a single sample stored as an array of [2]float64, with Sample[0] being the left channel and Sample[1] being the right channel
|
||||
type Sample [2]float64
|
||||
|
||||
// readSample reads a single sample from s.samples and truncates it from the buffer
|
||||
func (s *DefaultSpeaker) readSample() Sample {
|
||||
sample := s.samples[0]
|
||||
s.samples = s.samples[1:]
|
||||
return sample
|
||||
}
|
||||
|
||||
// Play initializes the Streamer and sets s.isPlaying to true
|
||||
func (ds *DefaultSpeaker) Play(s audio.Streamer) {
|
||||
ds.isPlaying = true
|
||||
ds.Streamer = s
|
||||
}
|
||||
|
||||
// streamToPlayer Streams up to len(s.samples) into s.samples, converts those into bytes for s.buf, and writes s.buf
|
||||
// to the underlying player
|
||||
func (s *DefaultSpeaker) streamToPlayer() error {
|
||||
n, ok := s.Stream(s.samples)
|
||||
if (n == len(s.samples) || 0 < n && n < len(s.samples)) && ok {
|
||||
r, err := s.read(s.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.buf = s.buf[:r]
|
||||
_, err = s.player.Write(s.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we drained the streamer while while reading,
|
||||
if n < len(s.samples) {
|
||||
s.isPlaying = false
|
||||
return nil
|
||||
}
|
||||
s.buf = make([]byte, s.bufferSize)
|
||||
}
|
||||
// this stream is already drained, set isPlaying to false
|
||||
if n == 0 && !ok {
|
||||
s.isPlaying = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update should be called during the main update loop in order to handle synchronization
|
||||
// If s.isPlaying, Update will stream all available samples to the underlying player once per update.
|
||||
func (s *DefaultSpeaker) Update() error {
|
||||
if s.isPlaying {
|
||||
return s.streamToPlayer()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package speaker
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/pixel/audio"
|
||||
"github.com/hajimehoshi/oto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
streamer audio.Streamer
|
||||
samples [][2]float64
|
||||
buf []byte
|
||||
player *oto.Player
|
||||
)
|
||||
|
||||
// Init initializes audio playback through speaker. Must be called before using this package. The
|
||||
// value of audio.SampleRate must be set (or left to the default) before calling this function.
|
||||
//
|
||||
// The bufferSize argument specifies the length of the speaker's buffer. On calling Update, speaker
|
||||
// pulls this amount of data from the playing Streamers and starts playing this data. Bigger
|
||||
// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
|
||||
// responsiveness and less delay.
|
||||
func Init(bufferSize time.Duration) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if player != nil {
|
||||
player.Close()
|
||||
player = nil
|
||||
}
|
||||
|
||||
numSamples := int(math.Ceil(bufferSize.Seconds() * audio.SampleRate))
|
||||
numBytes := numSamples * 4
|
||||
|
||||
var err error
|
||||
player, err = oto.NewPlayer(int(audio.SampleRate), 2, 2, numBytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize speaker")
|
||||
}
|
||||
|
||||
samples = make([][2]float64, numSamples)
|
||||
buf = make([]byte, numBytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Play starts playing the provided Streamer through the speaker.
|
||||
func Play(s audio.Streamer) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
streamer = s
|
||||
}
|
||||
|
||||
// Update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
|
||||
// data is sent and started playing.
|
||||
//
|
||||
// This function should be called at least once the duration of bufferSize given in Init, but it's
|
||||
// recommended to call it more frequently to avoid glitches.
|
||||
func Update() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if player == nil {
|
||||
panic("didn't call speaker.Init")
|
||||
}
|
||||
|
||||
// pull data from the streamer, if any
|
||||
n := 0
|
||||
if streamer != nil {
|
||||
var ok bool
|
||||
n, ok = streamer.Stream(samples)
|
||||
if !ok {
|
||||
streamer = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// convert samples to bytes
|
||||
for i := range samples[:n] {
|
||||
for c := range samples[i] {
|
||||
val := samples[i][c]
|
||||
if val < -1 {
|
||||
val = -1
|
||||
}
|
||||
if val > +1 {
|
||||
val = +1
|
||||
}
|
||||
valInt16 := int16(val * (1 << 15))
|
||||
low := byte(valInt16 % (1 << 8))
|
||||
high := byte(valInt16 / (1 << 8))
|
||||
buf[i*4+c*2+0] = low
|
||||
buf[i*4+c*2+1] = high
|
||||
}
|
||||
}
|
||||
|
||||
// fill the rest with silence
|
||||
for i := n * 4; i < len(buf); i++ {
|
||||
buf[i] = 0
|
||||
}
|
||||
|
||||
player.Write(buf)
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue