audio: Add AudioContext.Update (#177)

This commit is contained in:
Hajime Hoshi 2016-03-11 00:01:00 +09:00
parent 77b62615b2
commit 71312ba26f
7 changed files with 36 additions and 8 deletions

View File

@ -35,6 +35,7 @@ var (
)
func update(screen *ebiten.Image) error {
audioContext.Update()
if !audioLoaded {
select {
case <-audioLoadingDone:

View File

@ -131,6 +131,7 @@ func addNote() error {
}
func update(screen *ebiten.Image) error {
audioContext.Update()
defer func() {
frames++
}()

View File

@ -201,6 +201,7 @@ func init() {
}
func update(screen *ebiten.Image) error {
audioContext.Update()
updateInput()
for i, key := range keys {
if keyStates[key] != 1 {

View File

@ -79,6 +79,7 @@ func (s *stream) Seek(offset int64, whence int) (int64, error) {
var player *audio.Player
func update(screen *ebiten.Image) error {
audioContext.Update()
if player == nil {
var err error
player, err = audioContext.NewPlayer(&stream{})

View File

@ -24,6 +24,7 @@ import (
// TODO: In JavaScript, mixing should be done by WebAudio for performance.
type mixedPlayersStream struct {
context *Context
writtenBytes int
}
func min(a, b int) int {
@ -37,9 +38,19 @@ func (s *mixedPlayersStream) Read(b []byte) (int, error) {
s.context.Lock()
defer s.context.Unlock()
// TODO: 60 (FPS) is a magic number
bytesPerFrame := s.context.sampleRate * 4 / 60
x := s.context.frames*bytesPerFrame + len(b)
if x <= s.writtenBytes {
return 0, nil
}
l := len(b) / 4 * 4
if len(s.context.players) == 0 {
return 0, nil
l := min(len(b), x-s.writtenBytes)
copy(b, make([]byte, l))
s.writtenBytes += l
return l, nil
}
closed := []*Player{}
ll := l
@ -76,6 +87,7 @@ func (s *mixedPlayersStream) Read(b []byte) (int, error) {
for _, p := range closed {
delete(s.context.players, p)
}
s.writtenBytes += ll
return ll, nil
}
@ -86,6 +98,7 @@ type Context struct {
stream *mixedPlayersStream
players map[*Player]struct{}
innerPlayer *player
frames int
sync.Mutex
}
@ -95,7 +108,9 @@ func NewContext(sampleRate int) *Context {
sampleRate: sampleRate,
players: map[*Player]struct{}{},
}
c.stream = &mixedPlayersStream{c}
c.stream = &mixedPlayersStream{
context: c,
}
p, err := startPlaying(c.stream, c.sampleRate)
if err != nil {
panic(fmt.Sprintf("audio: NewContext error: %v", err))
@ -104,6 +119,18 @@ func NewContext(sampleRate int) *Context {
return c
}
// Update proceeds the inner (logical) time of the context by 1/60 second.
// This is expected to be called in the game's updating function (sync mode)
// or an independent goroutine with timers (unsync mode).
// In sync mode, the game logical time syncs the audio logical time and
// you will find audio stops when the game stops e.g. when the window is deactivated.
// In unsync mode, the audio never stops even when the game stops.
func (c *Context) Update() {
c.Lock()
defer c.Unlock()
c.frames++
}
type Player struct {
context *Context
src io.ReadSeeker

View File

@ -79,11 +79,6 @@ func (p *player) proceed() error {
const bytesPerSample = channelNum * 16 / 8
bufferSize := p.sampleRate * bytesPerSample / 60
c := int64(p.context.Get("currentTime").Float() * float64(p.sampleRate))
// Buffer size is relatively big and it is needed to check that c.positionInSample doesn't
// proceed too far away (#180).
if c+int64(bufferSize) < p.positionInSamples {
return nil
}
if p.positionInSamples < c {
p.positionInSamples = c
}

View File

@ -30,6 +30,8 @@ type VorbisStream struct {
buf *bytes.Reader
}
// TODO: Rename to DecodeVorbis or Decode?
func (c *Context) NewVorbisStream(src io.Reader) (*VorbisStream, error) {
decoded, channels, sampleRate, err := vorbis.Decode(src)
if err != nil {