audio: Bug fix: Multi source (or nodes) should be used to play multi sounds

This commit is contained in:
Hajime Hoshi 2015-01-26 01:20:56 +09:00
parent 2ea9ba8dcb
commit 9e7dfa2f16
4 changed files with 87 additions and 46 deletions

View File

@ -43,7 +43,7 @@ func init() {
a := amp[j] * math.Exp(-5*float64(i)/(x[j]*s))
v += a * math.Sin(float64(i)*twoPiF*float64(j+1)/s)
}
pcm[i] = v
pcm[i] = v / 5.0
}
}
@ -112,7 +112,7 @@ func update(screen *ebiten.Image) error {
if keyStates[key] != 1 {
continue
}
addNote(220*math.Exp2(float64(i)/12.0), 1.0/8.0)
addNote(220*math.Exp2(float64(i)/12.0), 1.0)
}
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
return nil

View File

@ -23,12 +23,12 @@ var audioEnabled = false
const bufferSize = 1024
const SampleRate = 44100
var nextInsertionPosition = 0
var currentPosition = 0
type channel struct {
l []int16
r []int16
l []int16
r []int16
nextInsertionPosition int
}
var MaxChannel = 32
@ -55,7 +55,7 @@ func Start() {
func isPlaying(channel int) bool {
ch := channels[channel]
return nextInsertionPosition < len(ch.l)
return ch.nextInsertionPosition < len(ch.l)
}
func channelAt(i int) *channel {
@ -88,8 +88,8 @@ func Play(channel int, l []int16, r []int16) bool {
if ch == nil {
return false
}
ch.l = append(ch.l, make([]int16, nextInsertionPosition-len(ch.l))...)
ch.r = append(ch.r, make([]int16, nextInsertionPosition-len(ch.r))...)
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
@ -112,7 +112,9 @@ func Queue(channel int, l []int16, r []int16) {
}
func Update() {
nextInsertionPosition += SampleRate / 60
for _, ch := range channels {
ch.nextInsertionPosition += SampleRate / 60
}
}
func min(a, b int) int {
@ -138,7 +140,31 @@ func isChannelsEmpty() bool {
return true
}
func loadChannelBuffers() (l, r []int16) {
func loadChannelBuffer(channel int) (l, r []int16) {
channelsLock.Lock()
defer channelsLock.Unlock()
if !audioEnabled {
return nil, nil
}
ch := channels[channel]
inputL := make([]int16, bufferSize)
inputR := make([]int16, bufferSize)
length := min(len(ch.l), bufferSize)
for i := 0; i < length; i++ {
inputL[i] = ch.l[i]
inputR[i] = ch.r[i]
}
usedLen := min(bufferSize, len(ch.l))
ch.l = ch.l[usedLen:]
ch.r = ch.r[usedLen:]
ch.nextInsertionPosition -= min(bufferSize, ch.nextInsertionPosition)
return inputL, inputR
}
/*func loadChannelBuffers() (l, r []int16) {
channelsLock.Lock()
defer channelsLock.Unlock()
@ -162,7 +188,7 @@ func loadChannelBuffers() (l, r []int16) {
ch.r = ch.r[usedLen:]
}
return inputL, inputR
}
}*/
func IsPlaying(channel int) bool {
channelsLock.Lock()

View File

@ -21,23 +21,18 @@ import (
)
// Keep this so as not to be destroyed by GC.
var node js.Object
var nodes = []js.Object{}
var context js.Object
func initialize() {
context = js.Global.Get("AudioContext").New()
// TODO: ScriptProcessorNode will be replaced with Audio WebWorker.
// https://developer.mozilla.org/ja/docs/Web/API/ScriptProcessorNode
node = context.Call("createScriptProcessor", bufferSize, 0, 2)
node.Call("addEventListener", "audioprocess", func(e js.Object) {
func audioProcess(channel int) func(e js.Object) {
return func(e js.Object) {
defer func() {
currentPosition += bufferSize
}()
l := e.Get("outputBuffer").Call("getChannelData", 0)
r := e.Get("outputBuffer").Call("getChannelData", 1)
inputL, inputR := loadChannelBuffers()
nextInsertionPosition -= min(bufferSize, nextInsertionPosition)
inputL, inputR := loadChannelBuffer(channel)
const max = 1 << 15
for i := 0; i < bufferSize; i++ {
// TODO: Use copyFromChannel?
@ -49,11 +44,24 @@ func initialize() {
l.SetIndex(i, float64(inputL[i])/max)
r.SetIndex(i, float64(inputR[i])/max)
}
})
}
}
func initialize() {
context = js.Global.Get("AudioContext").New()
// TODO: ScriptProcessorNode will be replaced with Audio WebWorker.
// https://developer.mozilla.org/ja/docs/Web/API/ScriptProcessorNode
for i := 0; i < MaxChannel; i++ {
node := context.Call("createScriptProcessor", bufferSize, 0, 2)
node.Call("addEventListener", "audioprocess", audioProcess(i))
nodes = append(nodes, node)
}
audioEnabled = true
}
func start() {
// TODO: For iOS, node should be connected with a buffer node.
node.Call("connect", context.Get("destination"))
for _, node := range nodes {
node.Call("connect", context.Get("destination"))
}
}

View File

@ -60,39 +60,46 @@ func initialize() {
}
audioEnabled = true
source := openal.NewSource()
sources := openal.NewSources(MaxChannel)
close(ch)
emptyBytes := make([]byte, 4*bufferSize)
const bufferNum = 16
buffers := openal.NewBuffers(bufferNum)
for _, buffer := range buffers {
buffer.SetData(openal.FormatStereo16, emptyBytes, SampleRate)
source.QueueBuffer(buffer)
}
source.Play()
if alErr := openal.GetError(); alErr != 0 {
panic(fmt.Sprintf("OpenAL error: %d", alErr))
for _, source := range sources {
const bufferNum = 16
buffers := openal.NewBuffers(bufferNum)
for _, buffer := range buffers {
buffer.SetData(openal.FormatStereo16, emptyBytes, SampleRate)
source.QueueBuffer(buffer)
}
source.Play()
if alErr := openal.GetError(); alErr != 0 {
panic(fmt.Sprintf("OpenAL error: %d", alErr))
}
}
for {
if source.State() != openal.Playing {
panic(fmt.Sprintf("invalid source state: %d (0x%[1]x)", source.State()))
oneProcessed := false
for channel, source := range sources {
if source.State() != openal.Playing {
panic(fmt.Sprintf("invalid source state: %d (0x%[1]x)", source.State()))
}
processed := source.BuffersProcessed()
if processed == 0 {
continue
}
oneProcessed = true
buffers := make([]openal.Buffer, processed)
source.UnqueueBuffers(buffers)
for _, buffer := range buffers {
l, r := loadChannelBuffer(channel)
b := toBytes(l, r)
buffer.SetData(openal.FormatStereo16, b, SampleRate)
source.QueueBuffer(buffer)
}
}
processed := source.BuffersProcessed()
if processed == 0 {
if !oneProcessed {
time.Sleep(1)
continue
}
buffers := make([]openal.Buffer, processed)
source.UnqueueBuffers(buffers)
for _, buffer := range buffers {
l, r := loadChannelBuffers()
b := toBytes(l, r)
buffer.SetData(openal.FormatStereo16, b, SampleRate)
source.QueueBuffer(buffer)
nextInsertionPosition -= min(bufferSize, nextInsertionPosition)
}
}
}()