audio: Use IO as source

This commit is contained in:
Hajime Hoshi 2016-02-11 02:04:23 +09:00
parent c6a431c9ab
commit d52118639d
6 changed files with 62 additions and 31 deletions

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"log" "log"
"math" "math"
@ -85,6 +86,13 @@ func toBytes(l, r []int16) []byte {
return b return b
} }
type stream struct {
*bytes.Reader
}
func (s *stream) Close() error {
return nil
}
func addNote() { func addNote() {
size := sampleRate / 60 size := sampleRate / 60
notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2} notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2}
@ -113,7 +121,8 @@ 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), sampleRate) b := toBytes(l, r)
audio.Queue(&stream{bytes.NewReader(b)}, sampleRate)
} }
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"image/color" "image/color"
"log" "log"
@ -69,10 +70,18 @@ func toBytes(l, r []int16) []byte {
return b return b
} }
type stream struct {
*bytes.Reader
}
func (s *stream) Close() error {
return nil
}
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, sampleRate) audio.Queue(&stream{bytes.NewReader(n)}, sampleRate)
return return
} }
length := len(pcm) * baseFreq / f length := len(pcm) * baseFreq / f
@ -89,7 +98,7 @@ func addNote(freq float64, vol float64) {
} }
n := toBytes(l, r) n := toBytes(l, r)
noteCache[f] = n noteCache[f] = n
audio.Queue(n, sampleRate) audio.Queue(&stream{bytes.NewReader(n)}, sampleRate)
} }
var keys = []ebiten.Key{ var keys = []ebiten.Key{

View File

@ -15,19 +15,24 @@
package audio package audio
import ( import (
"io"
"github.com/hajimehoshi/ebiten/internal/audio" "github.com/hajimehoshi/ebiten/internal/audio"
) )
type ReadSeekCloser interface {
io.ReadSeeker
io.Closer
}
// Queue queues the given data to the given channel. // Queue queues the given data to the given channel.
// 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 (16bits, 2 channel stereo, little endian) // src'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, sampleRate int) error { func Queue(src ReadSeekCloser, sampleRate int) error {
return audio.Queue(data, sampleRate) return audio.Queue(src, sampleRate)
} }
// TODO: Add Clear function

View File

@ -14,16 +14,26 @@
package audio package audio
import (
"io"
)
var audioEnabled = false var audioEnabled = false
type ReadSeekCloser interface {
io.ReadSeeker
io.Closer
}
type chunk struct { type chunk struct {
buffer []byte buffer ReadSeekCloser
sampleRate int sampleRate int
} }
func Init() { func Init() {
initialize() initialize()
} }
func Queue(data []byte, sampleRate int) error {
return playChunk(data, sampleRate) func Queue(src ReadSeekCloser, sampleRate int) error {
return playChunk(src, sampleRate)
} }

View File

@ -17,13 +17,15 @@
package audio package audio
import ( import (
"io/ioutil"
"github.com/gopherjs/gopherjs/js" "github.com/gopherjs/gopherjs/js"
) )
var context *js.Object var context *js.Object
type audioProcessor struct { type audioProcessor struct {
data []byte src ReadSeekCloser
sampleRate int sampleRate int
position float64 position float64
} }
@ -38,9 +40,14 @@ func toLR(data []byte) ([]int16, []int16) {
return l, r return l, r
} }
func (a *audioProcessor) playChunk(buf []byte) { func (a *audioProcessor) play() error {
// TODO: Reading all data at once is temporary implemntation. Treat this as stream.
buf, err := ioutil.ReadAll(a.src)
if err != nil {
return err
}
if len(buf) == 0 { if len(buf) == 0 {
return return nil
} }
const channelNum = 2 const channelNum = 2
const bytesPerSample = channelNum * 16 / 8 const bytesPerSample = channelNum * 16 / 8
@ -58,17 +65,16 @@ func (a *audioProcessor) playChunk(buf []byte) {
s.Call("connect", context.Get("destination")) s.Call("connect", context.Get("destination"))
s.Call("start", a.position) s.Call("start", a.position)
a.position += b.Get("duration").Float() a.position += b.Get("duration").Float()
return nil
} }
// TOOD: Implement IO version func playChunk(src ReadSeekCloser, sampleRate int) error {
func playChunk(data []byte, sampleRate int) error {
a := &audioProcessor{ a := &audioProcessor{
data: data, src: src,
sampleRate: sampleRate, sampleRate: sampleRate,
position: context.Get("currentTime").Float(), position: context.Get("currentTime").Float(),
} }
a.playChunk(data) return a.play()
return nil
} }
func initialize() { func initialize() {

View File

@ -17,25 +17,17 @@
package audio package audio
import ( import (
"bytes"
"time" "time"
"golang.org/x/mobile/exp/audio" "golang.org/x/mobile/exp/audio"
) )
type src struct {
*bytes.Reader
}
func (s *src) Close() error {
return nil
}
var players = map[*audio.Player]struct{}{} var players = map[*audio.Player]struct{}{}
func playChunk(data []byte, sampleRate int) error { func playChunk(src ReadSeekCloser, sampleRate int) error {
s := &src{bytes.NewReader(data)} // TODO: audio.NewPlayer interprets WAV header, which we don't want.
p, err := audio.NewPlayer(s, audio.Stereo16, int64(sampleRate)) // Use OpenAL or native API instead.
p, err := audio.NewPlayer(src, audio.Stereo16, int64(sampleRate))
if err != nil { if err != nil {
return err return err
} }