mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 11:18:54 +01:00
audio: Fix internal APIs (raw PCM data)
This commit is contained in:
parent
de59c93219
commit
00f582d435
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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?
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user