audio: add sampleRate arg to Queue: implementation turned to be simplified

This commit is contained in:
Hajime Hoshi 2016-02-10 02:40:07 +09:00
parent e8895d8f35
commit c6a431c9ab
6 changed files with 49 additions and 192 deletions

View File

@ -113,7 +113,7 @@ func addNote() {
vol := 1.0 / 16.0 vol := 1.0 / 16.0
square(l, vol, freq, 0.25) square(l, vol, freq, 0.25)
square(r, 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 { func update(screen *ebiten.Image) error {

View File

@ -72,7 +72,7 @@ func toBytes(l, r []int16) []byte {
func addNote(freq float64, vol float64) { func addNote(freq float64, vol float64) {
f := int(freq) f := int(freq)
if n, ok := noteCache[f]; ok { if n, ok := noteCache[f]; ok {
audio.Queue(n) audio.Queue(n, sampleRate)
return return
} }
length := len(pcm) * baseFreq / f length := len(pcm) * baseFreq / f
@ -89,7 +89,7 @@ func addNote(freq float64, vol float64) {
} }
n := toBytes(l, r) n := toBytes(l, r)
noteCache[f] = n noteCache[f] = n
audio.Queue(n) audio.Queue(n, sampleRate)
} }
var keys = []ebiten.Key{ var keys = []ebiten.Key{

View File

@ -22,12 +22,12 @@ import (
// The given data is queued to the end of the buffer. // The given data is queued to the end of the buffer.
// This may not be played immediately when data already exists in 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). // without a header (e.g. RIFF header).
// //
// TODO: Pass sample rate and num of channels. // TODO: Pass sample rate and num of channels.
func Queue(data []byte) bool { func Queue(data []byte, sampleRate int) error {
return audio.Queue(data) return audio.Queue(data, sampleRate)
} }
// TODO: Add Clear function // TODO: Add Clear function

View File

@ -14,106 +14,16 @@
package audio package audio
import (
"sync"
)
var audioEnabled = false var audioEnabled = false
const SampleRate = 44100 type chunk struct {
buffer []byte
type channel struct { sampleRate int
buffer []byte
}
const MaxChannel = 32
var channels = make([]*channel, MaxChannel)
func init() {
for i, _ := range channels {
channels[i] = &channel{
buffer: []byte{},
}
}
} }
func Init() { func Init() {
initialize() initialize()
} }
func Queue(data []byte, sampleRate int) error {
var channelsMutex = sync.Mutex{} return playChunk(data, sampleRate)
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
} }

View File

@ -17,21 +17,17 @@
package audio package audio
import ( import (
"time"
"github.com/gopherjs/gopherjs/js" "github.com/gopherjs/gopherjs/js"
) )
var context *js.Object var context *js.Object
type audioProcessor struct { type audioProcessor struct {
channel int data []byte
sampleRate int sampleRate int
position float64 position float64
} }
var audioProcessors [MaxChannel]*audioProcessor
func toLR(data []byte) ([]int16, []int16) { func toLR(data []byte) ([]int16, []int16) {
l := make([]int16, len(data)/4) l := make([]int16, len(data)/4)
r := 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() a.position += b.Get("duration").Float()
} }
func isPlaying(channel int) bool { // TOOD: Implement IO version
ch := channels[channel] func playChunk(data []byte, sampleRate int) error {
if 0 < len(ch.buffer) { a := &audioProcessor{
return true data: data,
sampleRate: sampleRate,
position: context.Get("currentTime").Float(),
} }
a := audioProcessors[channel] a.playChunk(data)
c := context.Get("currentTime").Float() return nil
return c < a.position
} }
func initialize() { func initialize() {
@ -89,25 +86,4 @@ func initialize() {
} }
context = class.New() context = class.New()
audioEnabled = true 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)
}
}()
} }

View File

@ -17,76 +17,47 @@
package audio package audio
import ( import (
"log" "bytes"
"runtime"
"time" "time"
"golang.org/x/mobile/exp/audio/al" "golang.org/x/mobile/exp/audio"
) )
func isPlaying(channel int) bool { type src struct {
ch := channels[channel] *bytes.Reader
return 0 < len(ch.buffer) }
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() { func initialize() {
// Creating OpenAL device must be done after initializing UI. I'm not sure the reason. audioEnabled = true
ch := make(chan struct{})
go func() { 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 { for {
oneProcessed := false deleted := []*audio.Player{}
for ch, source := range sources { for p, _ := range players {
processed := source.BuffersProcessed() if p.State() == audio.Stopped {
if processed == 0 { p.Close()
continue deleted = append(deleted, p)
}
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)
} }
} }
if !oneProcessed { for _, p := range deleted {
time.Sleep(1 * time.Millisecond) delete(players, p)
} }
time.Sleep(1 * time.Millisecond)
} }
}() }()
<-ch
} }