mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
audio: add sampleRate arg to Queue: implementation turned to be simplified
This commit is contained in:
parent
e8895d8f35
commit
c6a431c9ab
@ -113,7 +113,7 @@ func addNote() {
|
||||
vol := 1.0 / 16.0
|
||||
square(l, vol, freq, 0.25)
|
||||
square(r, vol, freq, 0.25)
|
||||
audio.Queue(toBytes(l, r))
|
||||
audio.Queue(toBytes(l, r), sampleRate)
|
||||
}
|
||||
|
||||
func update(screen *ebiten.Image) error {
|
||||
|
@ -72,7 +72,7 @@ func toBytes(l, r []int16) []byte {
|
||||
func addNote(freq float64, vol float64) {
|
||||
f := int(freq)
|
||||
if n, ok := noteCache[f]; ok {
|
||||
audio.Queue(n)
|
||||
audio.Queue(n, sampleRate)
|
||||
return
|
||||
}
|
||||
length := len(pcm) * baseFreq / f
|
||||
@ -89,7 +89,7 @@ func addNote(freq float64, vol float64) {
|
||||
}
|
||||
n := toBytes(l, r)
|
||||
noteCache[f] = n
|
||||
audio.Queue(n)
|
||||
audio.Queue(n, sampleRate)
|
||||
}
|
||||
|
||||
var keys = []ebiten.Key{
|
||||
|
@ -22,12 +22,12 @@ import (
|
||||
// The given data is queued to the end of the buffer.
|
||||
// This may not be played immediately when data already exists in the buffer.
|
||||
//
|
||||
// data's format must be linear PCM (44100Hz, 16bits, 2 channel stereo, little endian)
|
||||
// data's format must be linear PCM (16bits, 2 channel stereo, little endian)
|
||||
// without a header (e.g. RIFF header).
|
||||
//
|
||||
// TODO: Pass sample rate and num of channels.
|
||||
func Queue(data []byte) bool {
|
||||
return audio.Queue(data)
|
||||
func Queue(data []byte, sampleRate int) error {
|
||||
return audio.Queue(data, sampleRate)
|
||||
}
|
||||
|
||||
// TODO: Add Clear function
|
||||
|
@ -14,106 +14,16 @@
|
||||
|
||||
package audio
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var audioEnabled = false
|
||||
|
||||
const SampleRate = 44100
|
||||
|
||||
type channel struct {
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
const MaxChannel = 32
|
||||
|
||||
var channels = make([]*channel, MaxChannel)
|
||||
|
||||
func init() {
|
||||
for i, _ := range channels {
|
||||
channels[i] = &channel{
|
||||
buffer: []byte{},
|
||||
}
|
||||
}
|
||||
type chunk struct {
|
||||
buffer []byte
|
||||
sampleRate int
|
||||
}
|
||||
|
||||
func Init() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
var channelsMutex = sync.Mutex{}
|
||||
|
||||
func withChannels(f func()) {
|
||||
channelsMutex.Lock()
|
||||
defer channelsMutex.Unlock()
|
||||
f()
|
||||
}
|
||||
|
||||
func emptyChannel() *channel {
|
||||
for i, _ := range channels {
|
||||
if !isPlaying(i) {
|
||||
return channels[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Accept sample rate
|
||||
func Queue(data []byte) bool {
|
||||
result := true
|
||||
withChannels(func() {
|
||||
if !audioEnabled {
|
||||
result = false
|
||||
return
|
||||
}
|
||||
ch := emptyChannel()
|
||||
if ch == nil {
|
||||
result = false
|
||||
return
|
||||
}
|
||||
ch.buffer = append(ch.buffer, data...)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func isChannelsEmpty() bool {
|
||||
result := false
|
||||
withChannels(func() {
|
||||
if !audioEnabled {
|
||||
result = true
|
||||
return
|
||||
}
|
||||
|
||||
for _, ch := range channels {
|
||||
if 0 < len(ch.buffer) {
|
||||
return
|
||||
}
|
||||
}
|
||||
result = true
|
||||
return
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func loadChannelBuffer(channel int, bufferSize int) []byte {
|
||||
var r []byte
|
||||
withChannels(func() {
|
||||
if !audioEnabled {
|
||||
return
|
||||
}
|
||||
ch := channels[channel]
|
||||
length := min(len(ch.buffer), bufferSize)
|
||||
input := ch.buffer[:length]
|
||||
ch.buffer = ch.buffer[length:]
|
||||
r = input
|
||||
})
|
||||
return r
|
||||
func Queue(data []byte, sampleRate int) error {
|
||||
return playChunk(data, sampleRate)
|
||||
}
|
||||
|
@ -17,21 +17,17 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gopherjs/gopherjs/js"
|
||||
)
|
||||
|
||||
var context *js.Object
|
||||
|
||||
type audioProcessor struct {
|
||||
channel int
|
||||
data []byte
|
||||
sampleRate int
|
||||
position float64
|
||||
}
|
||||
|
||||
var audioProcessors [MaxChannel]*audioProcessor
|
||||
|
||||
func toLR(data []byte) ([]int16, []int16) {
|
||||
l := make([]int16, len(data)/4)
|
||||
r := make([]int16, len(data)/4)
|
||||
@ -64,14 +60,15 @@ func (a *audioProcessor) playChunk(buf []byte) {
|
||||
a.position += b.Get("duration").Float()
|
||||
}
|
||||
|
||||
func isPlaying(channel int) bool {
|
||||
ch := channels[channel]
|
||||
if 0 < len(ch.buffer) {
|
||||
return true
|
||||
// TOOD: Implement IO version
|
||||
func playChunk(data []byte, sampleRate int) error {
|
||||
a := &audioProcessor{
|
||||
data: data,
|
||||
sampleRate: sampleRate,
|
||||
position: context.Get("currentTime").Float(),
|
||||
}
|
||||
a := audioProcessors[channel]
|
||||
c := context.Get("currentTime").Float()
|
||||
return c < a.position
|
||||
a.playChunk(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func initialize() {
|
||||
@ -89,25 +86,4 @@ func initialize() {
|
||||
}
|
||||
context = class.New()
|
||||
audioEnabled = true
|
||||
for i := 0; i < len(audioProcessors); i++ {
|
||||
audioProcessors[i] = &audioProcessor{
|
||||
channel: i,
|
||||
sampleRate: 44100, // TODO: Change this for each chunks
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
const bufferSize = 2048
|
||||
c := context.Get("currentTime").Float()
|
||||
for _, a := range audioProcessors {
|
||||
if a.position < c {
|
||||
a.position = c
|
||||
}
|
||||
// TODO: 4 is a magic number
|
||||
a.playChunk(loadChannelBuffer(a.channel, bufferSize*4))
|
||||
}
|
||||
time.Sleep(0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -17,76 +17,47 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mobile/exp/audio/al"
|
||||
"golang.org/x/mobile/exp/audio"
|
||||
)
|
||||
|
||||
func isPlaying(channel int) bool {
|
||||
ch := channels[channel]
|
||||
return 0 < len(ch.buffer)
|
||||
type src struct {
|
||||
*bytes.Reader
|
||||
}
|
||||
|
||||
func (s *src) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var players = map[*audio.Player]struct{}{}
|
||||
|
||||
func playChunk(data []byte, sampleRate int) error {
|
||||
s := &src{bytes.NewReader(data)}
|
||||
p, err := audio.NewPlayer(s, audio.Stereo16, int64(sampleRate))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
players[p] = struct{}{}
|
||||
return p.Play()
|
||||
}
|
||||
|
||||
func initialize() {
|
||||
// Creating OpenAL device must be done after initializing UI. I'm not sure the reason.
|
||||
ch := make(chan struct{})
|
||||
audioEnabled = true
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
if err := al.OpenDevice(); err != nil {
|
||||
log.Printf("OpenAL initialize error: %v", err)
|
||||
close(ch)
|
||||
// Graceful ending: Audio is not available on Travis CI.
|
||||
return
|
||||
}
|
||||
|
||||
audioEnabled = true
|
||||
sources := al.GenSources(MaxChannel)
|
||||
close(ch)
|
||||
|
||||
const bufferSize = 2048
|
||||
emptyBytes := make([]byte, bufferSize)
|
||||
|
||||
for _, source := range sources {
|
||||
// 3 is the least number?
|
||||
// http://stackoverflow.com/questions/14932004/play-sound-with-openalstream
|
||||
const bufferNum = 4
|
||||
buffers := al.GenBuffers(bufferNum)
|
||||
for _, buffer := range buffers {
|
||||
buffer.BufferData(al.FormatStereo16, emptyBytes, SampleRate)
|
||||
source.QueueBuffers(buffer)
|
||||
}
|
||||
al.PlaySources(source)
|
||||
}
|
||||
|
||||
for {
|
||||
oneProcessed := false
|
||||
for ch, source := range sources {
|
||||
processed := source.BuffersProcessed()
|
||||
if processed == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
oneProcessed = true
|
||||
buffers := make([]al.Buffer, processed)
|
||||
source.UnqueueBuffers(buffers...)
|
||||
for _, buffer := range buffers {
|
||||
b := make([]byte, bufferSize)
|
||||
copy(b, loadChannelBuffer(ch, bufferSize))
|
||||
buffer.BufferData(al.FormatStereo16, b, SampleRate)
|
||||
source.QueueBuffers(buffer)
|
||||
}
|
||||
if source.State() == al.Stopped {
|
||||
al.RewindSources(source)
|
||||
al.PlaySources(source)
|
||||
deleted := []*audio.Player{}
|
||||
for p, _ := range players {
|
||||
if p.State() == audio.Stopped {
|
||||
p.Close()
|
||||
deleted = append(deleted, p)
|
||||
}
|
||||
}
|
||||
if !oneProcessed {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
for _, p := range deleted {
|
||||
delete(players, p)
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
<-ch
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user