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

View File

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

View File

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

View File

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

View File

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

View File

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