From 00f582d4353f3c44b25e943a2e914c4a364063f5 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 14 Jun 2015 02:37:20 +0900 Subject: [PATCH] audio: Fix internal APIs (raw PCM data) --- exp/audio/audio.go | 32 ++++--------------- exp/audio/internal/audio.go | 50 ++++++++++++------------------ exp/audio/internal/audio_js.go | 12 ++++++- exp/audio/internal/audio_openal.go | 28 +++-------------- 4 files changed, 40 insertions(+), 82 deletions(-) diff --git a/exp/audio/audio.go b/exp/audio/audio.go index a7b3f0f36..372db1d4e 100644 --- a/exp/audio/audio.go +++ b/exp/audio/audio.go @@ -15,9 +15,6 @@ package audio import ( - "bytes" - "encoding/binary" - "github.com/hajimehoshi/ebiten/exp/audio/internal" ) @@ -29,33 +26,16 @@ func SampleRate() int { // MaxChannel is a max number of channels. var MaxChannel = internal.MaxChannel -func toLR(data []byte) ([]int16, []int16) { - buf := bytes.NewReader(data) - b := make([]int16, len(data)/2) - if err := binary.Read(buf, binary.LittleEndian, b); err != nil { - panic(err) - } - l := make([]int16, len(data)/4) - r := make([]int16, len(data)/4) - for i := 0; i < len(data)/4; i++ { - l[i] = b[2*i] - r[i] = b[2*i+1] - } - return l, r -} - // Play appends the given data to the given channel. // // channel must be -1 or a channel index. If channel is -1, an empty channel is automatically selected. // If the channel is not empty, this function does nothing and returns false. This returns true otherwise. // -// data's format must be linear PCM (44100Hz, stereo, 16bit little endian). +// data's format must be linear PCM (44100Hz, 16bits, 2 channel stereo, little endian). // // This function is useful to play SE or a note of PCM synthesis immediately. func Play(channel int, data []byte) bool { - l, r := toLR(data) - // TODO: Pass data directly. - return internal.Play(channel, l, r) + return internal.Play(channel, data) } // Queue queues the given data to the given channel. @@ -63,16 +43,16 @@ func Play(channel int, data []byte) bool { // // channel must be a channel index. You can't give -1 to channel. // -// data's format must be linear PCM (44100Hz, stereo, 16bit little endian). +// data's format must be linear PCM (44100Hz, 16bits, 2 channel stereo, little endian). // // This function is useful to play streaming data. func Queue(channel int, data []byte) { - l, r := toLR(data) - // TODO: Pass data directly. - internal.Queue(channel, l, r) + internal.Queue(channel, data) } // IsPlaying returns a boolean value which indicates if the channel buffer has data to play. func IsPlaying(channel int) bool { return internal.IsPlaying(channel) } + +// TODO: Add Clear function diff --git a/exp/audio/internal/audio.go b/exp/audio/internal/audio.go index 22391e170..767a870f7 100644 --- a/exp/audio/internal/audio.go +++ b/exp/audio/internal/audio.go @@ -19,8 +19,7 @@ var audioEnabled = false const SampleRate = 44100 type channel struct { - l []int16 - r []int16 + buffer []byte nextInsertionPosition int } @@ -31,8 +30,7 @@ var channels = make([]*channel, MaxChannel) func init() { for i, _ := range channels { channels[i] = &channel{ - l: []int16{}, - r: []int16{}, + buffer: []byte{}, } } } @@ -44,7 +42,7 @@ func Init() { func isPlaying(channel int) bool { ch := channels[channel] - return ch.nextInsertionPosition < len(ch.l) + return ch.nextInsertionPosition < len(ch.buffer) } func channelAt(i int) *channel { @@ -68,7 +66,7 @@ func channelAt(i int) *channel { return ch } -func Play(channel int, l []int16, r []int16) bool { +func Play(channel int, data []byte) bool { ch := channelAt(channel) if ch == nil { return false @@ -77,38 +75,29 @@ func Play(channel int, l []int16, r []int16) bool { if !audioEnabled { return } - if len(l) != len(r) { - panic("len(l) must equal to len(r)") - } - d := ch.nextInsertionPosition - len(l) + d := ch.nextInsertionPosition - len(data) if 0 < d { - ch.l = append(ch.l, make([]int16, d)...) - ch.r = append(ch.r, make([]int16, d)...) + ch.buffer = append(ch.buffer, make([]byte, d)...) } - ch.l = append(ch.l, l...) - ch.r = append(ch.r, r...) + ch.buffer = append(ch.buffer, data...) }) return true } -func Queue(channel int, l []int16, r []int16) { +func Queue(channel int, data []byte) { withChannels(func() { 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...) + ch.buffer = append(ch.buffer, data...) }) } func Tick() { for _, ch := range channels { - if 0 < len(ch.l) { - ch.nextInsertionPosition += SampleRate / 60 // FPS + if 0 < len(ch.buffer) { + ch.nextInsertionPosition += SampleRate * 4 / 60 } else { ch.nextInsertionPosition = 0 } @@ -131,7 +120,7 @@ func isChannelsEmpty() bool { } for _, ch := range channels { - if 0 < len(ch.l) { + if 0 < len(ch.buffer) { return } } @@ -141,25 +130,24 @@ func isChannelsEmpty() bool { return result } -func loadChannelBuffer(channel int, bufferSize int) (l, r []int16) { +func loadChannelBuffer(channel int, bufferSize int) []byte { + var r []byte withChannels(func() { if !audioEnabled { return } ch := channels[channel] - length := min(len(ch.l), bufferSize) - inputL := ch.l[:length] - inputR := ch.r[:length] + length := min(len(ch.buffer), bufferSize) + input := ch.buffer[:length] ch.nextInsertionPosition -= length if ch.nextInsertionPosition < 0 { ch.nextInsertionPosition = 0 } - ch.l = ch.l[length:] - ch.r = ch.r[length:] - l, r = inputL, inputR + ch.buffer = ch.buffer[length:] + r = input }) - return + return r } func IsPlaying(channel int) bool { diff --git a/exp/audio/internal/audio_js.go b/exp/audio/internal/audio_js.go index f81cfd2e9..7130a44e7 100644 --- a/exp/audio/internal/audio_js.go +++ b/exp/audio/internal/audio_js.go @@ -37,12 +37,22 @@ type audioProcessor struct { channel int } +func toLR(data []byte) ([]int16, []int16) { + l := make([]int16, len(data)/4) + r := make([]int16, len(data)/4) + for i := 0; i < len(data)/4; i++ { + l[i] = int16(data[4*i]) | int16(data[4*i+1])<<8 + r[i] = int16(data[4*i+2]) | int16(data[4*i+3])<<8 + } + return l, r +} + func (a *audioProcessor) Process(e *js.Object) { // Can't use 'go' here. Probably it may cause race conditions. b := e.Get("outputBuffer") l := b.Call("getChannelData", 0) r := b.Call("getChannelData", 1) - inputL, inputR := loadChannelBuffer(a.channel, bufferSize) + inputL, inputR := toLR(loadChannelBuffer(a.channel, bufferSize*4)) const max = 1 << 15 for i := 0; i < len(inputL); i++ { // TODO: Use copyToChannel? diff --git a/exp/audio/internal/audio_openal.go b/exp/audio/internal/audio_openal.go index afcaf7ccc..879b5b292 100644 --- a/exp/audio/internal/audio_openal.go +++ b/exp/audio/internal/audio_openal.go @@ -17,8 +17,6 @@ package internal import ( - "bytes" - "encoding/binary" "fmt" "log" "runtime" @@ -36,24 +34,6 @@ func withChannels(f func()) { f() } -func toBytesWithPadding(l, r []int16, size int) []byte { - if len(l) != len(r) { - panic("len(l) must equal to len(r)") - } - b := &bytes.Buffer{} - for i := 0; i < len(l); i++ { - if err := binary.Write(b, binary.LittleEndian, []int16{l[i], r[i]}); err != nil { - panic(err) - } - } - if 0 < size-len(l) { - if err := binary.Write(b, binary.LittleEndian, make([]int16, (size-len(l))*2)); err != nil { - panic(err) - } - } - return b.Bytes() -} - func initialize() { // Creating OpenAL device must be done after initializing UI. I'm not sure the reason. ch := make(chan struct{}) @@ -75,8 +55,8 @@ func initialize() { sources := openal.NewSources(MaxChannel) close(ch) - const bufferSize = 512 - emptyBytes := make([]byte, 4*bufferSize) + const bufferSize = 2048 + emptyBytes := make([]byte, bufferSize) for _, source := range sources { // 3 is the least number? @@ -105,8 +85,8 @@ func initialize() { buffers := make([]openal.Buffer, processed) source.UnqueueBuffers(buffers) for _, buffer := range buffers { - l, r := loadChannelBuffer(ch, bufferSize) - b := toBytesWithPadding(l, r, bufferSize) + b := make([]byte, bufferSize) + copy(b, loadChannelBuffer(ch, bufferSize)) buffer.SetData(openal.FormatStereo16, b, SampleRate) source.QueueBuffer(buffer) }