diff --git a/exp/audio/internal/audio.go b/exp/audio/internal/audio.go index 749be5506..0cff16ecf 100644 --- a/exp/audio/internal/audio.go +++ b/exp/audio/internal/audio.go @@ -14,10 +14,6 @@ package internal -import ( - "sync" -) - var audioEnabled = false const SampleRate = 44100 @@ -34,9 +30,6 @@ var MaxChannel = 32 var channels = make([]*channel, MaxChannel) -// NOTE: In GopherJS, sync.Mutex blocks a function and requires gopherjs:blocking comments. -var channelsLock sync.Mutex - func init() { for i, _ := range channels { channels[i] = &channel{ @@ -72,41 +65,42 @@ func channelAt(i int) *channel { } func Play(channel int, l []int16, r []int16) bool { - channelsLock.Lock() - defer channelsLock.Unlock() + result := false + withChannels(func() { + if !audioEnabled { + return + } - if !audioEnabled { - return false - } - - if len(l) != len(r) { - panic("len(l) must equal to len(r)") - } - ch := channelAt(channel) - if ch == nil { - return false - } - ch.l = append(ch.l, make([]int16, ch.nextInsertionPosition-len(ch.l))...) - ch.r = append(ch.r, make([]int16, ch.nextInsertionPosition-len(ch.r))...) - ch.l = append(ch.l, l...) - ch.r = append(ch.r, r...) - return true + if len(l) != len(r) { + panic("len(l) must equal to len(r)") + } + ch := channelAt(channel) + if ch == nil { + return + } + ch.l = append(ch.l, make([]int16, ch.nextInsertionPosition-len(ch.l))...) + ch.r = append(ch.r, make([]int16, ch.nextInsertionPosition-len(ch.r))...) + ch.l = append(ch.l, l...) + ch.r = append(ch.r, r...) + result = true + return + }) + return result } func Queue(channel int, l []int16, r []int16) { - channelsLock.Lock() - defer channelsLock.Unlock() + withChannels(func() { + if !audioEnabled { + return + } - if !audioEnabled { - return - } - - if len(l) != len(r) { - panic("len(l) must equal to len(r)") - } - ch := channels[channel] - ch.l = append(ch.l, l...) - ch.r = append(ch.r, r...) + if len(l) != len(r) { + panic("len(l) must equal to len(r)") + } + ch := channels[channel] + ch.l = append(ch.l, l...) + ch.r = append(ch.r, r...) + }) } func Tick() { @@ -123,48 +117,51 @@ func min(a, b int) int { } func isChannelsEmpty() bool { - channelsLock.Lock() - defer channelsLock.Unlock() - - if !audioEnabled { - return true - } - - for _, ch := range channels { - if 0 < len(ch.l) { - return false + result := false + withChannels(func() { + if !audioEnabled { + result = true + return } - } - return true + + for _, ch := range channels { + if 0 < len(ch.l) { + return + } + } + result = true + return + }) + return result } func loadChannelBuffer(channel int, bufferSize int) (l, r []int16) { - channelsLock.Lock() - defer channelsLock.Unlock() + withChannels(func() { + if !audioEnabled { + return + } - if !audioEnabled { - return nil, nil - } - - ch := channels[channel] - length := min(len(ch.l), bufferSize) - inputL := make([]int16, length) - inputR := make([]int16, length) - copy(inputL, ch.l[:length]) - copy(inputR, ch.r[:length]) - ch.l = ch.l[length:] - ch.r = ch.r[length:] - ch.nextInsertionPosition -= min(bufferSize, ch.nextInsertionPosition) - return inputL, inputR + ch := channels[channel] + length := min(len(ch.l), bufferSize) + inputL := make([]int16, length) + inputR := make([]int16, length) + copy(inputL, ch.l[:length]) + copy(inputR, ch.r[:length]) + ch.l = ch.l[length:] + ch.r = ch.r[length:] + ch.nextInsertionPosition -= min(bufferSize, ch.nextInsertionPosition) + l, r = inputL, inputR + }) + return } func IsPlaying(channel int) bool { - channelsLock.Lock() - defer channelsLock.Unlock() - - if !audioEnabled { - return false - } - - return isPlaying(channel) + result := false + withChannels(func() { + if !audioEnabled { + return + } + result = isPlaying(channel) + }) + return result } diff --git a/exp/audio/internal/audio_js.go b/exp/audio/internal/audio_js.go index 41fd81731..55f467eca 100644 --- a/exp/audio/internal/audio_js.go +++ b/exp/audio/internal/audio_js.go @@ -20,35 +20,41 @@ import ( "github.com/gopherjs/gopherjs/js" ) +func withChannels(f func()) { + f() +} + // Keep this so as not to be destroyed by GC. -var nodes = []js.Object{} -var context js.Object +var ( + nodes = []js.Object{} + dummies = []js.Object{} // Dummy source nodes for iOS. + context js.Object +) const bufferSize = 1024 func audioProcess(channel int) func(e js.Object) { return func(e js.Object) { - go func() { - defer func() { - currentPosition += bufferSize - }() - - b := e.Get("outputBuffer") - l := b.Call("getChannelData", 0) - r := b.Call("getChannelData", 1) - inputL, inputR := loadChannelBuffer(channel, bufferSize) - const max = 1 << 15 - for i := 0; i < len(inputL); i++ { - // TODO: Use copyToChannel? - if len(inputL) <= i { - l.SetIndex(i, 0) - r.SetIndex(i, 0) - continue - } - l.SetIndex(i, float64(inputL[i])/max) - r.SetIndex(i, float64(inputR[i])/max) - } + // Can't use 'go' here. Probably it may cause race conditions. + defer func() { + currentPosition += bufferSize }() + + b := e.Get("outputBuffer") + l := b.Call("getChannelData", 0) + r := b.Call("getChannelData", 1) + inputL, inputR := loadChannelBuffer(channel, bufferSize) + const max = 1 << 15 + for i := 0; i < bufferSize; i++ { + // TODO: Use copyToChannel? + if len(inputL) <= i { + l.SetIndex(i, 0) + r.SetIndex(i, 0) + continue + } + l.SetIndex(i, float64(inputL[i])/max) + r.SetIndex(i, float64(inputR[i])/max) + } } } @@ -65,13 +71,18 @@ func initialize() { node := context.Call("createScriptProcessor", bufferSize, 0, 2) node.Call("addEventListener", "audioprocess", audioProcess(i)) nodes = append(nodes, node) + + dummy := context.Call("createBufferSource") + dummies = append(dummies, dummy) } audioEnabled = true } func start() { - // TODO: For iOS, node should be connected with a buffer node. - for _, node := range nodes { - node.Call("connect", context.Get("destination")) + destination := context.Get("destination") + for i, node := range nodes { + dummy := dummies[i] + dummy.Call("connect", node) + node.Call("connect", destination) } } diff --git a/exp/audio/internal/audio_openal.go b/exp/audio/internal/audio_openal.go index 873adfd5a..9f38c7307 100644 --- a/exp/audio/internal/audio_openal.go +++ b/exp/audio/internal/audio_openal.go @@ -23,9 +23,18 @@ import ( "github.com/timshannon/go-openal/openal" "log" "runtime" + "sync" "time" ) +var channelsMutex = sync.Mutex{} + +func withChannels(f func()) { + channelsMutex.Lock() + defer channelsMutex.Unlock() + f() +} + func toBytesWithPadding(l, r []int16, size int) []byte { if len(l) != len(r) { panic("len(l) must equal to len(r)")