mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-11 19:48:54 +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
|
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 {
|
||||||
|
@ -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{
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user