audio: Reuse al.Source objects (crash still happens on piano)

This commit is contained in:
Hajime Hoshi 2016-02-12 21:39:48 +09:00
parent f0d7a811e1
commit b2724e8694
4 changed files with 79 additions and 33 deletions

View File

@ -48,6 +48,7 @@ const (
freqGS = 830.6
)
// Twinkle, Twinkle, Little Star
const score = `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR`
var scoreIndex = 0
@ -86,14 +87,7 @@ func toBytes(l, r []int16) []byte {
return b
}
type stream struct {
*bytes.Reader
}
func (s *stream) Close() error {
return nil
}
func addNote() {
func addNote() error {
size := sampleRate / 60
notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2}
@ -122,8 +116,12 @@ func addNote() {
square(l, vol, freq, 0.25)
square(r, vol, freq, 0.25)
b := toBytes(l, r)
p := audio.NewPlayer(&stream{bytes.NewReader(b)}, sampleRate)
p, err := audio.NewPlayer(bytes.NewReader(b), sampleRate)
if err != nil {
return err
}
p.Play()
return nil
}
func update(screen *ebiten.Image) error {
@ -131,7 +129,9 @@ func update(screen *ebiten.Image) error {
frames++
}()
if frames%30 == 0 {
addNote()
if err := addNote(); err != nil {
return err
}
}
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
return nil

View File

@ -78,12 +78,18 @@ func (s *stream) Close() error {
return nil
}
func addNote(freq float64, vol float64) {
func addNote(freq float64, vol float64) error {
f := int(freq)
if n, ok := noteCache[f]; ok {
p := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate)
p, err := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate)
if err == audio.ErrTooManyPlayers {
return nil
}
if err != nil {
return err
}
p.Play()
return
return nil
}
length := len(pcm) * baseFreq / f
l := make([]int16, length)
@ -99,8 +105,15 @@ func addNote(freq float64, vol float64) {
}
n := toBytes(l, r)
noteCache[f] = n
p := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate)
p, err := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate)
if err == audio.ErrTooManyPlayers {
return nil
}
if err != nil {
return err
}
p.Play()
return nil
}
var keys = []ebiten.Key{
@ -193,7 +206,9 @@ func update(screen *ebiten.Image) error {
if keyStates[key] != 1 {
continue
}
addNote(220*math.Exp2(float64(i-1)/12.0), 1.0)
if err := addNote(220*math.Exp2(float64(i-1)/12.0), 1.0); err != nil {
return err
}
}
screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})

View File

@ -15,6 +15,7 @@
package audio
import (
"errors"
"io"
)
@ -30,10 +31,12 @@ type Player struct {
// without a header (e.g. RIFF header).
//
// TODO: Pass sample rate and num of channels.
func NewPlayer(src io.ReadSeeker, sampleRate int) *Player {
func NewPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
return newPlayer(src, sampleRate)
}
var ErrTooManyPlayers = errors.New("audio: too many players exist")
func (p *Player) Play() error {
return p.player.play()
}

View File

@ -26,13 +26,14 @@ import (
"golang.org/x/mobile/exp/audio/al"
)
type readSeekCloser struct {
io.ReadSeeker
type alSourceCacheEntry struct {
source al.Source
sampleRate int
}
func (r *readSeekCloser) Close() error {
return nil
}
const maxSourceNum = 32
var alSourceCache = []alSourceCacheEntry{}
type player struct {
alSource al.Source
@ -43,27 +44,52 @@ type player struct {
var m sync.Mutex
var sn = 0
func newPlayer(src io.ReadSeeker, sampleRate int) *Player {
m.Lock()
if err := al.OpenDevice(); err != nil {
panic(fmt.Sprintf("audio: OpenAL initialization failed: %v", err))
func newAlSource(sampleRate int) (al.Source, error) {
for _, e := range alSourceCache {
if e.sampleRate != sampleRate {
continue
}
s := e.source.State()
if s != al.Initial && s != al.Stopped {
continue
}
return e.source, nil
}
if maxSourceNum <= len(alSourceCache) {
return 0, ErrTooManyPlayers
}
// TODO: Too many generating sources may cause error. Limit the number of sources.
s := al.GenSources(1)
if err := al.Error(); err != 0 {
panic(fmt.Sprintf("audio: al.GenSources error: %d", err))
}
e := alSourceCacheEntry{
source: s[0],
sampleRate: sampleRate,
}
alSourceCache = append(alSourceCache, e)
return s[0], nil
}
func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
m.Lock()
if e := al.OpenDevice(); e != nil {
m.Unlock()
return nil, fmt.Errorf("audio: OpenAL initialization failed: %v", e)
}
s, err := newAlSource(sampleRate)
if err != nil {
m.Unlock()
return nil, err
}
m.Unlock()
p := &player{
alSource: s[0],
alSource: s,
alBuffers: []al.Buffer{},
source: src,
sampleRate: sampleRate,
}
runtime.SetFinalizer(p, (*player).close)
return &Player{p}
return &Player{p}, nil
}
const bufferSize = 1024
@ -106,7 +132,8 @@ func (p *player) play() error {
// TODO: What if play is already called?
emptyBytes := make([]byte, bufferSize)
m.Lock()
bufs := al.GenBuffers(8)
queued := p.alSource.BuffersQueued()
bufs := al.GenBuffers(8 - int(queued))
for _, buf := range bufs {
// Note that the third argument of only the first buffer is used.
buf.BufferData(al.FormatStereo16, emptyBytes, int32(p.sampleRate))
@ -134,7 +161,8 @@ func (p *player) play() error {
func (p *player) close() error {
m.Lock()
if p.alSource != 0 {
al.DeleteSources(p.alSource)
al.RewindSources(p.alSource)
al.StopSources(p.alSource)
p.alSource = 0
}
if 0 < len(p.alBuffers) {
@ -142,7 +170,7 @@ func (p *player) close() error {
p.alBuffers = []al.Buffer{}
}
if err := al.Error(); err != 0 {
panic(fmt.Sprintf("audio: close error: %d", err))
panic(fmt.Sprintf("audio: closing error: %d", err))
}
m.Unlock()
runtime.SetFinalizer(p, nil)