addresses some review comments
This commit is contained in:
parent
32ff29438d
commit
8e571bfe8e
|
@ -1,60 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/faiface/pixel/audio"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sine struct {
|
|
||||||
freq float64
|
|
||||||
rate float64
|
|
||||||
time float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sine) Stream(samples [][2]float64) (n int, ok bool) {
|
|
||||||
for i := 0; i < len(samples)-1; i += 2 {
|
|
||||||
val := math.Sin(math.Pi*s.time*s.freq) / 1.1
|
|
||||||
s.time += 1 / s.rate
|
|
||||||
valI := int16((1 << 15) * val)
|
|
||||||
low := float64(valI % (1 << 8))
|
|
||||||
high := float64(valI / (1 << 8))
|
|
||||||
samples[i][0] = low
|
|
||||||
samples[i][1] = high
|
|
||||||
samples[i+1][0] = low
|
|
||||||
samples[i+1][1] = high
|
|
||||||
}
|
|
||||||
return len(samples), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := run()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() error {
|
|
||||||
audio.SampleRate = 44100
|
|
||||||
const bufSize = 1 << 13
|
|
||||||
|
|
||||||
speaker, err := audio.NewDefaultSpeaker(bufSize)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &sine{freq: 440, rate: audio.SampleRate, time: 0}
|
|
||||||
|
|
||||||
speaker.Play(s)
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := speaker.Update()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
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
|
||||||
|
}
|
118
audio/speaker.go
118
audio/speaker.go
|
@ -1,118 +0,0 @@
|
||||||
package audio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/oto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Speaker interface {
|
|
||||||
Play(Streamer)
|
|
||||||
Update() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultSpeaker struct {
|
|
||||||
Streamer
|
|
||||||
isPlaying bool
|
|
||||||
samples [][2]float64
|
|
||||||
player *oto.Player
|
|
||||||
buf []uint8
|
|
||||||
bufferSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrBufferMustBePowerOf2 = errors.New("Buffer passed to Read must be a power of 2")
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultSpeaker) eof() bool {
|
|
||||||
return len(s.samples) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Sample [2]float64
|
|
||||||
|
|
||||||
func (s *DefaultSpeaker) readSample() Sample {
|
|
||||||
sample := s.samples[0]
|
|
||||||
s.samples = s.samples[1:]
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultSpeaker(bufferSize int) (*DefaultSpeaker, error) {
|
|
||||||
p, err := oto.NewPlayer(int(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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *DefaultSpeaker) Play(s Streamer) {
|
|
||||||
bs.isPlaying = true
|
|
||||||
bs.Streamer = s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *DefaultSpeaker) Update() error {
|
|
||||||
|
|
||||||
if b.isPlaying {
|
|
||||||
n, ok := b.Stream(b.samples)
|
|
||||||
if n == len(b.samples) && ok {
|
|
||||||
r, err := b.Read(b.buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.buf = b.buf[:r]
|
|
||||||
_, err = b.player.Write(b.buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.buf = make([]byte, b.bufferSize)
|
|
||||||
}
|
|
||||||
// we're read bytes but drained the streamer, so copy data and stop playing
|
|
||||||
if n > 0 && n < len(b.samples) && ok {
|
|
||||||
r, err := b.Read(b.buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.buf = b.buf[:r]
|
|
||||||
_, err = b.player.Write(b.buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.isPlaying = false
|
|
||||||
}
|
|
||||||
// this stream is already drained, set isPlaying to false
|
|
||||||
if n == 0 && !ok {
|
|
||||||
b.isPlaying = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue