From fe91d341ac9f20f514c38dae403c9605381300a7 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 25 Jan 2015 01:51:51 +0900 Subject: [PATCH] audio: Implement for desktops with OpenAL (#104) --- example/audio/main.go | 2 +- internal/audio/audio.go | 69 ++++++++++++++++++++++++---------- internal/audio/audio_openal.go | 68 +++++++++++++++++++++++++++++++-- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/example/audio/main.go b/example/audio/main.go index 3083bcc2c..62da4a78f 100644 --- a/example/audio/main.go +++ b/example/audio/main.go @@ -111,7 +111,7 @@ func update(screen *ebiten.Image) error { } func main() { - if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Rotate (Ebiten Demo)"); err != nil { + if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Audio (Ebiten Demo)"); err != nil { log.Fatal(err) } } diff --git a/internal/audio/audio.go b/internal/audio/audio.go index 3a86a56a0..d1ce7346f 100644 --- a/internal/audio/audio.go +++ b/internal/audio/audio.go @@ -14,6 +14,10 @@ package audio +import ( + "sync" +) + const bufferSize = 1024 const SampleRate = 44100 @@ -28,6 +32,7 @@ type channel struct { var MaxChannel = 32 var channels = make([]*channel, MaxChannel) +var channelsLock sync.Mutex func init() { for i, _ := range channels { @@ -46,8 +51,30 @@ func Start() { start() } +func isPlaying(channel int) bool { + ch := channels[channel] + return nextInsertionPosition < len(ch.l) +} + +func channelAt(i int) *channel { + if i == -1 { + for i, _ := range channels { + if !isPlaying(i) { + return channels[i] + } + } + return nil + } + if !isPlaying(i) { + return channels[i] + } + return nil +} + func Play(channel int, l []float32, r []float32) bool { - // TODO: Mutex (especially for OpenAL) + channelsLock.Lock() + defer channelsLock.Unlock() + if len(l) != len(r) { panic("len(l) must equal to len(r)") } @@ -63,7 +90,9 @@ func Play(channel int, l []float32, r []float32) bool { } func Queue(channel int, l []float32, r []float32) { - // TODO: Mutex (especially for OpenAL) + channelsLock.Lock() + defer channelsLock.Unlock() + if len(l) != len(r) { panic("len(l) must equal to len(r)") } @@ -76,21 +105,6 @@ func Update() { nextInsertionPosition += SampleRate / 60 } -func channelAt(i int) *channel { - if i == -1 { - for i, _ := range channels { - if !IsPlaying(i) { - return channels[i] - } - } - return nil - } - if !IsPlaying(i) { - return channels[i] - } - return nil -} - func min(a, b int) int { if a < b { return a @@ -98,7 +112,22 @@ func min(a, b int) int { return b } +func isChannelsEmpty() bool { + channelsLock.Lock() + defer channelsLock.Unlock() + + for _, ch := range channels { + if 0 < len(ch.l) { + return false + } + } + return true +} + func loadChannelBuffers() (l, r []float32) { + channelsLock.Lock() + defer channelsLock.Unlock() + inputL := make([]float32, bufferSize) inputR := make([]float32, bufferSize) for _, ch := range channels { @@ -118,6 +147,8 @@ func loadChannelBuffers() (l, r []float32) { } func IsPlaying(channel int) bool { - ch := channels[channel] - return nextInsertionPosition < len(ch.l) + channelsLock.Lock() + defer channelsLock.Unlock() + + return isPlaying(channel) } diff --git a/internal/audio/audio_openal.go b/internal/audio/audio_openal.go index 3aad59a4c..b26f3ad7a 100644 --- a/internal/audio/audio_openal.go +++ b/internal/audio/audio_openal.go @@ -17,10 +17,35 @@ package audio import ( + "bytes" + "encoding/binary" + "fmt" "github.com/timshannon/go-openal/openal" + "math" "runtime" + "time" ) +func toBytes(l, r []float32) []byte { + if len(l) != len(r) { + panic("len(l) must equal to len(r)") + } + b := &bytes.Buffer{} + for i, _ := range l { + l := int16(l[i] * math.MaxInt16) + r := int16(r[i] * math.MaxInt16) + if err := binary.Write(b, binary.LittleEndian, []int16{l, r}); err != nil { + panic(err) + } + } + return b.Bytes() +} + +type sample struct { + l []float32 + r []float32 +} + func initialize() { ch := make(chan struct{}) go func() { @@ -30,15 +55,50 @@ func initialize() { context := device.CreateContext() context.Activate() - buffer := openal.NewBuffer() - //buffer.SetData(openal.FormatStereo16) - _ = buffer + source := openal.NewSource() + + if alErr := openal.GetError(); alErr != 0 { + panic(fmt.Sprintf("OpenAL initialize error: %d", alErr)) + } 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 { + if source.State() != openal.Playing { + panic(fmt.Sprintf("invalid source state: %d (0x%[1]x)", source.State())) + } + processed := source.BuffersProcessed() + if processed == 0 { + 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) + } + } }() <-ch } func start() { - // TODO: Implement + // Do nothing }