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

View File

@ -15,24 +15,102 @@
package audio
import (
"errors"
"fmt"
"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?
type Context struct {
sampleRate int
sampleRate int
stream *mixedPlayersStream
innerPlayer *player
players map[*Player]struct{}
sync.Mutex
}
func NewContext(sampleRate int) *Context {
return &Context{
// TODO: Panic if one context exists.
c := &Context{
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 {
player *player
context *Context
src io.ReadSeeker
buf []byte
}
// 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.
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 {
return p.player.play()
p.context.Lock()
defer p.context.Unlock()
p.context.players[p] = struct{}{}
return nil
}
func (p *Player) Close() error {
return p.player.close()
// TODO: IsPlaying
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
type player struct {
src io.ReadSeeker
src io.Reader
sampleRate int
position float64
bufferSource *js.Object
@ -49,7 +49,7 @@ func initialize() bool {
return true
}
func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
func newPlayer(src io.Reader, sampleRate int) (*player, error) {
if context == nil {
if !initialize() {
panic("audio couldn't be initialized")
@ -62,7 +62,7 @@ func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
position: context.Get("currentTime").Float(),
bufferSource: nil,
}
return &Player{p}, nil
return p, nil
}
func toLR(data []byte) ([]int16, []int16) {

View File

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