diff --git a/examples/audio/main.go b/examples/audio/main.go index 444b4b5f5..bd1540171 100644 --- a/examples/audio/main.go +++ b/examples/audio/main.go @@ -35,6 +35,7 @@ var ( ) func update(screen *ebiten.Image) error { + audioContext.Update() if !audioLoaded { select { case <-audioLoadingDone: diff --git a/examples/pcm/main.go b/examples/pcm/main.go index 9ef4fe623..4a734b204 100644 --- a/examples/pcm/main.go +++ b/examples/pcm/main.go @@ -131,6 +131,7 @@ func addNote() error { } func update(screen *ebiten.Image) error { + audioContext.Update() defer func() { frames++ }() diff --git a/examples/piano/main.go b/examples/piano/main.go index 305a32230..2a5a89924 100644 --- a/examples/piano/main.go +++ b/examples/piano/main.go @@ -201,6 +201,7 @@ func init() { } func update(screen *ebiten.Image) error { + audioContext.Update() updateInput() for i, key := range keys { if keyStates[key] != 1 { diff --git a/examples/sinewave/main.go b/examples/sinewave/main.go index 954c86ed1..5b07eab33 100644 --- a/examples/sinewave/main.go +++ b/examples/sinewave/main.go @@ -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{}) diff --git a/exp/audio/audio.go b/exp/audio/audio.go index fdf5b4245..edff2eb96 100644 --- a/exp/audio/audio.go +++ b/exp/audio/audio.go @@ -23,7 +23,8 @@ import ( // TODO: In JavaScript, mixing should be done by WebAudio for performance. type mixedPlayersStream struct { - context *Context + 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 diff --git a/exp/audio/audio_js.go b/exp/audio/audio_js.go index 11cbd09cf..6a04dbf8d 100644 --- a/exp/audio/audio_js.go +++ b/exp/audio/audio_js.go @@ -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 } diff --git a/exp/audio/vorbis.go b/exp/audio/vorbis.go index 6ecd7f35d..5447bfbbc 100644 --- a/exp/audio/vorbis.go +++ b/exp/audio/vorbis.go @@ -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 {