mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-11 19:48:54 +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() {
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
|
|
||||||
package audio
|
package audio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
const bufferSize = 1024
|
const bufferSize = 1024
|
||||||
const SampleRate = 44100
|
const SampleRate = 44100
|
||||||
|
|
||||||
@ -28,6 +32,7 @@ type channel struct {
|
|||||||
var MaxChannel = 32
|
var MaxChannel = 32
|
||||||
|
|
||||||
var channels = make([]*channel, MaxChannel)
|
var channels = make([]*channel, MaxChannel)
|
||||||
|
var channelsLock sync.Mutex
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
for i, _ := range channels {
|
for i, _ := range channels {
|
||||||
@ -46,8 +51,30 @@ func Start() {
|
|||||||
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 {
|
func Play(channel int, l []float32, r []float32) bool {
|
||||||
// TODO: Mutex (especially for OpenAL)
|
channelsLock.Lock()
|
||||||
|
defer channelsLock.Unlock()
|
||||||
|
|
||||||
if len(l) != len(r) {
|
if len(l) != len(r) {
|
||||||
panic("len(l) must equal to 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) {
|
func Queue(channel int, l []float32, r []float32) {
|
||||||
// TODO: Mutex (especially for OpenAL)
|
channelsLock.Lock()
|
||||||
|
defer channelsLock.Unlock()
|
||||||
|
|
||||||
if len(l) != len(r) {
|
if len(l) != len(r) {
|
||||||
panic("len(l) must equal to len(r)")
|
panic("len(l) must equal to len(r)")
|
||||||
}
|
}
|
||||||
@ -76,21 +105,6 @@ func Update() {
|
|||||||
nextInsertionPosition += SampleRate / 60
|
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 {
|
func min(a, b int) int {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
@ -98,7 +112,22 @@ func min(a, b int) int {
|
|||||||
return b
|
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) {
|
func loadChannelBuffers() (l, r []float32) {
|
||||||
|
channelsLock.Lock()
|
||||||
|
defer channelsLock.Unlock()
|
||||||
|
|
||||||
inputL := make([]float32, bufferSize)
|
inputL := make([]float32, bufferSize)
|
||||||
inputR := make([]float32, bufferSize)
|
inputR := make([]float32, bufferSize)
|
||||||
for _, ch := range channels {
|
for _, ch := range channels {
|
||||||
@ -118,6 +147,8 @@ func loadChannelBuffers() (l, r []float32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsPlaying(channel int) bool {
|
func IsPlaying(channel int) bool {
|
||||||
ch := channels[channel]
|
channelsLock.Lock()
|
||||||
return nextInsertionPosition < len(ch.l)
|
defer channelsLock.Unlock()
|
||||||
|
|
||||||
|
return isPlaying(channel)
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,35 @@
|
|||||||
package audio
|
package audio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"github.com/timshannon/go-openal/openal"
|
"github.com/timshannon/go-openal/openal"
|
||||||
|
"math"
|
||||||
"runtime"
|
"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() {
|
func initialize() {
|
||||||
ch := make(chan struct{})
|
ch := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
@ -30,15 +55,50 @@ func initialize() {
|
|||||||
context := device.CreateContext()
|
context := device.CreateContext()
|
||||||
context.Activate()
|
context.Activate()
|
||||||
|
|
||||||
buffer := openal.NewBuffer()
|
source := openal.NewSource()
|
||||||
//buffer.SetData(openal.FormatStereo16)
|
|
||||||
_ = buffer
|
if alErr := openal.GetError(); alErr != 0 {
|
||||||
|
panic(fmt.Sprintf("OpenAL initialize error: %d", alErr))
|
||||||
|
}
|
||||||
|
|
||||||
close(ch)
|
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
|
<-ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
// TODO: Implement
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user