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