mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 10:48:53 +01:00
audio: Implement for desktops with OpenAL (#104)
This commit is contained in:
parent
a8e3b2b619
commit
fe91d341ac
@ -111,7 +111,7 @@ func update(screen *ebiten.Image) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Rotate (Ebiten Demo)"); err != nil {
|
||||
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Audio (Ebiten Demo)"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,10 @@
|
||||
|
||||
package audio
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
const bufferSize = 1024
|
||||
const SampleRate = 44100
|
||||
|
||||
@ -28,6 +32,7 @@ type channel struct {
|
||||
var MaxChannel = 32
|
||||
|
||||
var channels = make([]*channel, MaxChannel)
|
||||
var channelsLock sync.Mutex
|
||||
|
||||
func init() {
|
||||
for i, _ := range channels {
|
||||
@ -46,8 +51,30 @@ func Start() {
|
||||
start()
|
||||
}
|
||||
|
||||
func isPlaying(channel int) bool {
|
||||
ch := channels[channel]
|
||||
return nextInsertionPosition < len(ch.l)
|
||||
}
|
||||
|
||||
func channelAt(i int) *channel {
|
||||
if i == -1 {
|
||||
for i, _ := range channels {
|
||||
if !isPlaying(i) {
|
||||
return channels[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !isPlaying(i) {
|
||||
return channels[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Play(channel int, l []float32, r []float32) bool {
|
||||
// TODO: Mutex (especially for OpenAL)
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
if len(l) != len(r) {
|
||||
panic("len(l) must equal to len(r)")
|
||||
}
|
||||
@ -63,7 +90,9 @@ func Play(channel int, l []float32, r []float32) bool {
|
||||
}
|
||||
|
||||
func Queue(channel int, l []float32, r []float32) {
|
||||
// TODO: Mutex (especially for OpenAL)
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
if len(l) != len(r) {
|
||||
panic("len(l) must equal to len(r)")
|
||||
}
|
||||
@ -76,21 +105,6 @@ func Update() {
|
||||
nextInsertionPosition += SampleRate / 60
|
||||
}
|
||||
|
||||
func channelAt(i int) *channel {
|
||||
if i == -1 {
|
||||
for i, _ := range channels {
|
||||
if !IsPlaying(i) {
|
||||
return channels[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !IsPlaying(i) {
|
||||
return channels[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
@ -98,7 +112,22 @@ func min(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
func isChannelsEmpty() bool {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
for _, ch := range channels {
|
||||
if 0 < len(ch.l) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadChannelBuffers() (l, r []float32) {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
inputL := make([]float32, bufferSize)
|
||||
inputR := make([]float32, bufferSize)
|
||||
for _, ch := range channels {
|
||||
@ -118,6 +147,8 @@ func loadChannelBuffers() (l, r []float32) {
|
||||
}
|
||||
|
||||
func IsPlaying(channel int) bool {
|
||||
ch := channels[channel]
|
||||
return nextInsertionPosition < len(ch.l)
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
return isPlaying(channel)
|
||||
}
|
||||
|
@ -17,10 +17,35 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/timshannon/go-openal/openal"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func toBytes(l, r []float32) []byte {
|
||||
if len(l) != len(r) {
|
||||
panic("len(l) must equal to len(r)")
|
||||
}
|
||||
b := &bytes.Buffer{}
|
||||
for i, _ := range l {
|
||||
l := int16(l[i] * math.MaxInt16)
|
||||
r := int16(r[i] * math.MaxInt16)
|
||||
if err := binary.Write(b, binary.LittleEndian, []int16{l, r}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
type sample struct {
|
||||
l []float32
|
||||
r []float32
|
||||
}
|
||||
|
||||
func initialize() {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
@ -30,15 +55,50 @@ func initialize() {
|
||||
context := device.CreateContext()
|
||||
context.Activate()
|
||||
|
||||
buffer := openal.NewBuffer()
|
||||
//buffer.SetData(openal.FormatStereo16)
|
||||
_ = buffer
|
||||
source := openal.NewSource()
|
||||
|
||||
if alErr := openal.GetError(); alErr != 0 {
|
||||
panic(fmt.Sprintf("OpenAL initialize error: %d", alErr))
|
||||
}
|
||||
|
||||
close(ch)
|
||||
|
||||
emptyBytes := make([]byte, 4*bufferSize)
|
||||
|
||||
const bufferNum = 16
|
||||
buffers := openal.NewBuffers(bufferNum)
|
||||
for _, buffer := range buffers {
|
||||
buffer.SetData(openal.FormatStereo16, emptyBytes, SampleRate)
|
||||
source.QueueBuffer(buffer)
|
||||
}
|
||||
source.Play()
|
||||
if alErr := openal.GetError(); alErr != 0 {
|
||||
panic(fmt.Sprintf("OpenAL error: %d", alErr))
|
||||
}
|
||||
|
||||
for {
|
||||
if source.State() != openal.Playing {
|
||||
panic(fmt.Sprintf("invalid source state: %d (0x%[1]x)", source.State()))
|
||||
}
|
||||
processed := source.BuffersProcessed()
|
||||
if processed == 0 {
|
||||
time.Sleep(1)
|
||||
continue
|
||||
}
|
||||
buffers := make([]openal.Buffer, processed)
|
||||
source.UnqueueBuffers(buffers)
|
||||
for _, buffer := range buffers {
|
||||
l, r := loadChannelBuffers()
|
||||
b := toBytes(l, r)
|
||||
buffer.SetData(openal.FormatStereo16, b, SampleRate)
|
||||
source.QueueBuffer(buffer)
|
||||
nextInsertionPosition -= min(bufferSize, nextInsertionPosition)
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-ch
|
||||
}
|
||||
|
||||
func start() {
|
||||
// TODO: Implement
|
||||
// Do nothing
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user