audio: Fix internal APIs (raw PCM data)

This commit is contained in:
Hajime Hoshi 2015-06-14 02:37:20 +09:00
parent de59c93219
commit 00f582d435
4 changed files with 40 additions and 82 deletions

View File

@ -15,9 +15,6 @@
package audio package audio
import ( import (
"bytes"
"encoding/binary"
"github.com/hajimehoshi/ebiten/exp/audio/internal" "github.com/hajimehoshi/ebiten/exp/audio/internal"
) )
@ -29,33 +26,16 @@ func SampleRate() int {
// MaxChannel is a max number of channels. // MaxChannel is a max number of channels.
var MaxChannel = internal.MaxChannel 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. // 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. // 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. // 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. // This function is useful to play SE or a note of PCM synthesis immediately.
func Play(channel int, data []byte) bool { func Play(channel int, data []byte) bool {
l, r := toLR(data) return internal.Play(channel, data)
// TODO: Pass data directly.
return internal.Play(channel, l, r)
} }
// Queue queues the given data to the given channel. // 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. // 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. // This function is useful to play streaming data.
func Queue(channel int, data []byte) { func Queue(channel int, data []byte) {
l, r := toLR(data) internal.Queue(channel, data)
// TODO: Pass data directly.
internal.Queue(channel, l, r)
} }
// IsPlaying returns a boolean value which indicates if the channel buffer has data to play. // IsPlaying returns a boolean value which indicates if the channel buffer has data to play.
func IsPlaying(channel int) bool { func IsPlaying(channel int) bool {
return internal.IsPlaying(channel) return internal.IsPlaying(channel)
} }
// TODO: Add Clear function

View File

@ -19,8 +19,7 @@ var audioEnabled = false
const SampleRate = 44100 const SampleRate = 44100
type channel struct { type channel struct {
l []int16 buffer []byte
r []int16
nextInsertionPosition int nextInsertionPosition int
} }
@ -31,8 +30,7 @@ var channels = make([]*channel, MaxChannel)
func init() { func init() {
for i, _ := range channels { for i, _ := range channels {
channels[i] = &channel{ channels[i] = &channel{
l: []int16{}, buffer: []byte{},
r: []int16{},
} }
} }
} }
@ -44,7 +42,7 @@ func Init() {
func isPlaying(channel int) bool { func isPlaying(channel int) bool {
ch := channels[channel] ch := channels[channel]
return ch.nextInsertionPosition < len(ch.l) return ch.nextInsertionPosition < len(ch.buffer)
} }
func channelAt(i int) *channel { func channelAt(i int) *channel {
@ -68,7 +66,7 @@ func channelAt(i int) *channel {
return ch return ch
} }
func Play(channel int, l []int16, r []int16) bool { func Play(channel int, data []byte) bool {
ch := channelAt(channel) ch := channelAt(channel)
if ch == nil { if ch == nil {
return false return false
@ -77,38 +75,29 @@ func Play(channel int, l []int16, r []int16) bool {
if !audioEnabled { if !audioEnabled {
return return
} }
if len(l) != len(r) { d := ch.nextInsertionPosition - len(data)
panic("len(l) must equal to len(r)")
}
d := ch.nextInsertionPosition - len(l)
if 0 < d { if 0 < d {
ch.l = append(ch.l, make([]int16, d)...) ch.buffer = append(ch.buffer, make([]byte, d)...)
ch.r = append(ch.r, make([]int16, d)...)
} }
ch.l = append(ch.l, l...) ch.buffer = append(ch.buffer, data...)
ch.r = append(ch.r, r...)
}) })
return true return true
} }
func Queue(channel int, l []int16, r []int16) { func Queue(channel int, data []byte) {
withChannels(func() { withChannels(func() {
if !audioEnabled { if !audioEnabled {
return return
} }
if len(l) != len(r) {
panic("len(l) must equal to len(r)")
}
ch := channels[channel] ch := channels[channel]
ch.l = append(ch.l, l...) ch.buffer = append(ch.buffer, data...)
ch.r = append(ch.r, r...)
}) })
} }
func Tick() { func Tick() {
for _, ch := range channels { for _, ch := range channels {
if 0 < len(ch.l) { if 0 < len(ch.buffer) {
ch.nextInsertionPosition += SampleRate / 60 // FPS ch.nextInsertionPosition += SampleRate * 4 / 60
} else { } else {
ch.nextInsertionPosition = 0 ch.nextInsertionPosition = 0
} }
@ -131,7 +120,7 @@ func isChannelsEmpty() bool {
} }
for _, ch := range channels { for _, ch := range channels {
if 0 < len(ch.l) { if 0 < len(ch.buffer) {
return return
} }
} }
@ -141,25 +130,24 @@ func isChannelsEmpty() bool {
return result return result
} }
func loadChannelBuffer(channel int, bufferSize int) (l, r []int16) { func loadChannelBuffer(channel int, bufferSize int) []byte {
var r []byte
withChannels(func() { withChannels(func() {
if !audioEnabled { if !audioEnabled {
return return
} }
ch := channels[channel] ch := channels[channel]
length := min(len(ch.l), bufferSize) length := min(len(ch.buffer), bufferSize)
inputL := ch.l[:length] input := ch.buffer[:length]
inputR := ch.r[:length]
ch.nextInsertionPosition -= length ch.nextInsertionPosition -= length
if ch.nextInsertionPosition < 0 { if ch.nextInsertionPosition < 0 {
ch.nextInsertionPosition = 0 ch.nextInsertionPosition = 0
} }
ch.l = ch.l[length:] ch.buffer = ch.buffer[length:]
ch.r = ch.r[length:] r = input
l, r = inputL, inputR
}) })
return return r
} }
func IsPlaying(channel int) bool { func IsPlaying(channel int) bool {

View File

@ -37,12 +37,22 @@ type audioProcessor struct {
channel int 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) { func (a *audioProcessor) Process(e *js.Object) {
// Can't use 'go' here. Probably it may cause race conditions. // Can't use 'go' here. Probably it may cause race conditions.
b := e.Get("outputBuffer") b := e.Get("outputBuffer")
l := b.Call("getChannelData", 0) l := b.Call("getChannelData", 0)
r := b.Call("getChannelData", 1) r := b.Call("getChannelData", 1)
inputL, inputR := loadChannelBuffer(a.channel, bufferSize) inputL, inputR := toLR(loadChannelBuffer(a.channel, bufferSize*4))
const max = 1 << 15 const max = 1 << 15
for i := 0; i < len(inputL); i++ { for i := 0; i < len(inputL); i++ {
// TODO: Use copyToChannel? // TODO: Use copyToChannel?

View File

@ -17,8 +17,6 @@
package internal package internal
import ( import (
"bytes"
"encoding/binary"
"fmt" "fmt"
"log" "log"
"runtime" "runtime"
@ -36,24 +34,6 @@ func withChannels(f func()) {
f() 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() { func initialize() {
// Creating OpenAL device must be done after initializing UI. I'm not sure the reason. // Creating OpenAL device must be done after initializing UI. I'm not sure the reason.
ch := make(chan struct{}) ch := make(chan struct{})
@ -75,8 +55,8 @@ func initialize() {
sources := openal.NewSources(MaxChannel) sources := openal.NewSources(MaxChannel)
close(ch) close(ch)
const bufferSize = 512 const bufferSize = 2048
emptyBytes := make([]byte, 4*bufferSize) emptyBytes := make([]byte, bufferSize)
for _, source := range sources { for _, source := range sources {
// 3 is the least number? // 3 is the least number?
@ -105,8 +85,8 @@ func initialize() {
buffers := make([]openal.Buffer, processed) buffers := make([]openal.Buffer, processed)
source.UnqueueBuffers(buffers) source.UnqueueBuffers(buffers)
for _, buffer := range buffers { for _, buffer := range buffers {
l, r := loadChannelBuffer(ch, bufferSize) b := make([]byte, bufferSize)
b := toBytesWithPadding(l, r, bufferSize) copy(b, loadChannelBuffer(ch, bufferSize))
buffer.SetData(openal.FormatStereo16, b, SampleRate) buffer.SetData(openal.FormatStereo16, b, SampleRate)
source.QueueBuffer(buffer) source.QueueBuffer(buffer)
} }