mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 10:48:53 +01:00
audio: Bug fix: Multi source (or nodes) should be used to play multi sounds
This commit is contained in:
parent
2ea9ba8dcb
commit
9e7dfa2f16
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
Loading…
Reference in New Issue
Block a user