audio: Mixing by Go

This commit is contained in:
Hajime Hoshi 2016-03-03 11:57:25 +09:00
parent 58c5e5e655
commit 3859bc7421
4 changed files with 113 additions and 42 deletions

View File

@ -88,9 +88,6 @@ func addNote(freq float64, vol float64) error {
f := int(freq) f := int(freq)
if n, ok := noteCache[f]; ok { if n, ok := noteCache[f]; ok {
p, err := audioContext.NewPlayer(&stream{bytes.NewReader(n)}) p, err := audioContext.NewPlayer(&stream{bytes.NewReader(n)})
if err == audio.ErrTooManyPlayers {
return nil
}
if err != nil { if err != nil {
return err return err
} }
@ -112,9 +109,6 @@ func addNote(freq float64, vol float64) error {
n := toBytes(l, r) n := toBytes(l, r)
noteCache[f] = n noteCache[f] = n
p, err := audioContext.NewPlayer(&stream{bytes.NewReader(n)}) p, err := audioContext.NewPlayer(&stream{bytes.NewReader(n)})
if err == audio.ErrTooManyPlayers {
return nil
}
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,24 +15,102 @@
package audio package audio
import ( import (
"errors" "fmt"
"io" "io"
"sync"
) )
// TODO: In JavaScript, mixing should be done by WebAudio for performance.
type mixedPlayersStream struct {
context *Context
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (s *mixedPlayersStream) Read(b []byte) (int, error) {
s.context.Lock()
defer s.context.Unlock()
l := len(b)
if len(s.context.players) == 0 {
copy(b, make([]byte, l))
return l, nil
}
closed := []*Player{}
for p := range s.context.players {
ll := l - len(p.buf)
if ll <= 0 {
continue
}
b := make([]byte, ll)
n, err := p.src.Read(b)
if 0 < n {
p.buf = append(p.buf, b[:n]...)
}
if err == io.EOF {
if len(p.buf) < l {
p.buf = append(p.buf, make([]byte, l-len(p.buf))...)
}
closed = append(closed, p)
}
}
resultLen := l
for p := range s.context.players {
resultLen = min(len(p.buf)/2*2, resultLen)
}
for i := 0; i < resultLen/2; i++ {
x := int16(0)
for p := range s.context.players {
// TODO: Overflow check to avoid distortion
x += int16(p.buf[2*i]) | (int16(p.buf[2*i+1]) << 8)
}
b[2*i] = byte(x)
b[2*i+1] = byte(x >> 8)
}
for p := range s.context.players {
p.buf = p.buf[resultLen:]
}
for _, p := range closed {
delete(s.context.players, p)
}
return resultLen, nil
}
// TODO: Enable to specify the format like Mono8? // TODO: Enable to specify the format like Mono8?
type Context struct { type Context struct {
sampleRate int sampleRate int
stream *mixedPlayersStream
innerPlayer *player
players map[*Player]struct{}
sync.Mutex
} }
func NewContext(sampleRate int) *Context { func NewContext(sampleRate int) *Context {
return &Context{ // TODO: Panic if one context exists.
c := &Context{
sampleRate: sampleRate, sampleRate: sampleRate,
players: map[*Player]struct{}{},
} }
c.stream = &mixedPlayersStream{c}
var err error
c.innerPlayer, err = newPlayer(c.stream, c.sampleRate)
if err != nil {
panic(fmt.Sprintf("audio: NewContext error: %v", err))
}
c.innerPlayer.play()
return c
} }
type Player struct { type Player struct {
player *player context *Context
src io.ReadSeeker
buf []byte
} }
// NewPlayer creates a new player with the given data to the given channel. // NewPlayer creates a new player with the given data to the given channel.
@ -44,15 +122,31 @@ type Player struct {
// //
// TODO: Pass sample rate and num of channels. // TODO: Pass sample rate and num of channels.
func (c *Context) NewPlayer(src io.ReadSeeker) (*Player, error) { func (c *Context) NewPlayer(src io.ReadSeeker) (*Player, error) {
return newPlayer(src, c.sampleRate) c.Lock()
} defer c.Unlock()
var ErrTooManyPlayers = errors.New("audio: too many players exist") p := &Player{
context: c,
src: src,
buf: []byte{},
}
return p, nil
}
func (p *Player) Play() error { func (p *Player) Play() error {
return p.player.play() p.context.Lock()
defer p.context.Unlock()
p.context.players[p] = struct{}{}
return nil
} }
func (p *Player) Close() error { // TODO: IsPlaying
return p.player.close()
func (p *Player) Stop() error {
p.context.Lock()
defer p.context.Unlock()
delete(p.context.players, p)
return nil
} }

View File

@ -26,7 +26,7 @@ import (
var context *js.Object var context *js.Object
type player struct { type player struct {
src io.ReadSeeker src io.Reader
sampleRate int sampleRate int
position float64 position float64
bufferSource *js.Object bufferSource *js.Object
@ -49,7 +49,7 @@ func initialize() bool {
return true return true
} }
func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) { func newPlayer(src io.Reader, sampleRate int) (*player, error) {
if context == nil { if context == nil {
if !initialize() { if !initialize() {
panic("audio couldn't be initialized") panic("audio couldn't be initialized")
@ -62,7 +62,7 @@ func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
position: context.Get("currentTime").Float(), position: context.Get("currentTime").Float(),
bufferSource: nil, bufferSource: nil,
} }
return &Player{p}, nil return p, nil
} }
func toLR(data []byte) ([]int16, []int16) { func toLR(data []byte) ([]int16, []int16) {

View File

@ -27,39 +27,23 @@ import (
) )
const ( const (
maxSourceNum = 32
maxBufferNum = 8 maxBufferNum = 8
) )
var totalBufferNum = 0 var totalBufferNum = 0
var playerCache = []*player{}
type player struct { type player struct {
alSource al.Source alSource al.Source
alBuffers []al.Buffer alBuffers []al.Buffer
source io.ReadSeeker source io.Reader
sampleRate int sampleRate int
isClosed bool isClosed bool
} }
var m sync.Mutex var m sync.Mutex
func newPlayerFromCache(src io.ReadSeeker, sampleRate int) (*player, error) { // TODO: Unify this to newPlayer
for _, p := range playerCache { func newPlayerFromCache(src io.Reader, sampleRate int) (*player, error) {
if p.sampleRate != sampleRate {
continue
}
if !p.isClosed {
continue
}
p.source = src
p.isClosed = false
return p, nil
}
if maxSourceNum <= len(playerCache) {
return nil, ErrTooManyPlayers
}
s := al.GenSources(1) s := al.GenSources(1)
if err := al.Error(); err != 0 { if err := al.Error(); err != 0 {
panic(fmt.Sprintf("audio: al.GenSources error: %d", err)) panic(fmt.Sprintf("audio: al.GenSources error: %d", err))
@ -71,11 +55,10 @@ func newPlayerFromCache(src io.ReadSeeker, sampleRate int) (*player, error) {
sampleRate: sampleRate, sampleRate: sampleRate,
} }
runtime.SetFinalizer(p, (*player).close) runtime.SetFinalizer(p, (*player).close)
playerCache = append(playerCache, p)
return p, nil return p, nil
} }
func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) { func newPlayer(src io.Reader, sampleRate int) (*player, error) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
@ -87,7 +70,7 @@ func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Player{p}, nil return p, nil
} }
const bufferSize = 1024 const bufferSize = 1024
@ -147,7 +130,7 @@ func (p *player) play() error {
if 0 < n { if 0 < n {
p.alBuffers = append(p.alBuffers, al.GenBuffers(n)...) p.alBuffers = append(p.alBuffers, al.GenBuffers(n)...)
totalBufferNum += n totalBufferNum += n
if maxSourceNum*maxBufferNum < totalBufferNum { if maxBufferNum < totalBufferNum {
panic("audio: too many buffers are created") panic("audio: too many buffers are created")
} }
} }