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
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

View File

@ -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 {

View File

@ -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?

View File

@ -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)
}