mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
internal/cbackend: simplify the audio API
This commit is contained in:
parent
e6131894a7
commit
011e5061df
@ -19,8 +19,6 @@ package cbackend
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/cbackend"
|
||||
)
|
||||
@ -29,31 +27,25 @@ type Context struct {
|
||||
sampleRate int
|
||||
channelNum int
|
||||
bitDepthInBytes int
|
||||
|
||||
players *players
|
||||
}
|
||||
|
||||
func NewContext(sampleRate, channelNum, bitDepthInBytes int) (*Context, chan struct{}, error) {
|
||||
cbackend.OpenAudio(sampleRate, channelNum, bitDepthInBytes)
|
||||
c := &Context{
|
||||
sampleRate: sampleRate,
|
||||
channelNum: channelNum,
|
||||
bitDepthInBytes: bitDepthInBytes,
|
||||
players: newPlayers(),
|
||||
}
|
||||
cbackend.OpenAudio(sampleRate, channelNum, c.players.read)
|
||||
ready := make(chan struct{})
|
||||
close(ready)
|
||||
return c, ready, nil
|
||||
}
|
||||
|
||||
func (c *Context) NewPlayer(r io.Reader) *Player {
|
||||
cond := sync.NewCond(&sync.Mutex{})
|
||||
p := &Player{
|
||||
context: c,
|
||||
src: r,
|
||||
volume: 1,
|
||||
cond: cond,
|
||||
onWritten: cond.Signal,
|
||||
}
|
||||
runtime.SetFinalizer(p, (*Player).Close)
|
||||
return p
|
||||
func (c *Context) NewPlayer(src io.Reader) *Player {
|
||||
return newPlayer(c, src)
|
||||
}
|
||||
|
||||
func (c *Context) Suspend() error {
|
||||
@ -70,259 +62,6 @@ func (c *Context) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) oneBufferSize() int {
|
||||
return int(float64(c.sampleRate*c.channelNum*c.bitDepthInBytes) * cbackend.AudioBufferSizeInSeconds())
|
||||
}
|
||||
|
||||
func (c *Context) MaxBufferSize() int {
|
||||
// TODO: This must be audio.maxBufferSize(p.context.sampleRate). Avoid the duplication.
|
||||
return c.oneBufferSize() * 2
|
||||
}
|
||||
|
||||
type playerState int
|
||||
|
||||
const (
|
||||
playerStatePaused playerState = iota
|
||||
playerStatePlaying
|
||||
playerStateClosed
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
context *Context
|
||||
src io.Reader
|
||||
v *cbackend.AudioPlayer
|
||||
state playerState
|
||||
volume float64
|
||||
cond *sync.Cond
|
||||
err error
|
||||
buf []byte
|
||||
|
||||
onWritten func()
|
||||
}
|
||||
|
||||
func (p *Player) Pause() {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
if p.state == playerStateClosed {
|
||||
return
|
||||
}
|
||||
if p.v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.v.Pause()
|
||||
p.state = playerStatePaused
|
||||
p.cond.Signal()
|
||||
}
|
||||
|
||||
func (p *Player) Play() {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
if p.state == playerStateClosed {
|
||||
return
|
||||
}
|
||||
|
||||
var runloop bool
|
||||
if p.v == nil {
|
||||
p.v = cbackend.CreateAudioPlayer(p.onWritten)
|
||||
runloop = true
|
||||
}
|
||||
|
||||
p.v.SetVolume(p.volume)
|
||||
p.v.Play()
|
||||
|
||||
// Prepare the first data as soon as possible, or the audio can get stuck.
|
||||
// TODO: Get the appropriate buffer size from the C++ side.
|
||||
if p.buf == nil {
|
||||
n := p.context.oneBufferSize()
|
||||
if max := p.context.MaxBufferSize() - p.UnplayedBufferSize(); n > max {
|
||||
n = max
|
||||
}
|
||||
p.buf = make([]byte, n)
|
||||
}
|
||||
n, err := p.src.Read(p.buf)
|
||||
if err != nil && err != io.EOF {
|
||||
p.setErrorImpl(err)
|
||||
return
|
||||
}
|
||||
if n > 0 {
|
||||
p.writeImpl(p.buf[:n])
|
||||
}
|
||||
|
||||
if runloop {
|
||||
go p.loop()
|
||||
}
|
||||
p.state = playerStatePlaying
|
||||
p.cond.Signal()
|
||||
}
|
||||
|
||||
func (p *Player) IsPlaying() bool {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
return p.state == playerStatePlaying
|
||||
}
|
||||
|
||||
func (p *Player) Reset() {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
if p.state == playerStateClosed {
|
||||
return
|
||||
}
|
||||
p.state = playerStatePaused
|
||||
|
||||
if p.v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.v.Close(true)
|
||||
p.v = nil
|
||||
p.cond.Signal()
|
||||
}
|
||||
|
||||
func (p *Player) Volume() float64 {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
if p.v == nil {
|
||||
return p.volume
|
||||
}
|
||||
return p.v.Volume()
|
||||
}
|
||||
|
||||
func (p *Player) SetVolume(volume float64) {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
p.volume = volume
|
||||
if p.v == nil {
|
||||
return
|
||||
}
|
||||
p.v.SetVolume(volume)
|
||||
}
|
||||
|
||||
func (p *Player) UnplayedBufferSize() int {
|
||||
if p.v == nil {
|
||||
return 0
|
||||
}
|
||||
return p.v.UnplayedBufferSize()
|
||||
}
|
||||
|
||||
func (p *Player) Err() error {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
return p.err
|
||||
}
|
||||
|
||||
func (p *Player) Close() error {
|
||||
runtime.SetFinalizer(p, nil)
|
||||
return p.close(true)
|
||||
}
|
||||
|
||||
func (p *Player) close(remove bool) error {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
return p.closeImpl(remove)
|
||||
}
|
||||
|
||||
func (p *Player) closeImpl(remove bool) error {
|
||||
if p.state == playerStateClosed {
|
||||
return p.err
|
||||
}
|
||||
|
||||
if p.v != nil {
|
||||
p.v.Close(false)
|
||||
p.v = nil
|
||||
}
|
||||
if remove {
|
||||
p.state = playerStateClosed
|
||||
p.onWritten = nil
|
||||
} else {
|
||||
p.state = playerStatePaused
|
||||
}
|
||||
p.cond.Signal()
|
||||
return p.err
|
||||
}
|
||||
|
||||
func (p *Player) setError(err error) {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
p.setErrorImpl(err)
|
||||
}
|
||||
|
||||
func (p *Player) setErrorImpl(err error) {
|
||||
if p.state != playerStateClosed && p.v != nil {
|
||||
p.v.Close(true)
|
||||
p.v = nil
|
||||
}
|
||||
p.err = err
|
||||
p.state = playerStateClosed
|
||||
p.cond.Signal()
|
||||
}
|
||||
|
||||
func (p *Player) shouldWait() bool {
|
||||
if p.v == nil {
|
||||
return false
|
||||
}
|
||||
switch p.state {
|
||||
case playerStatePaused:
|
||||
return true
|
||||
case playerStatePlaying:
|
||||
return p.v.UnplayedBufferSize() >= p.context.MaxBufferSize()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Player) waitUntilUnpaused() bool {
|
||||
p.cond.L.Lock()
|
||||
defer p.cond.L.Unlock()
|
||||
|
||||
for p.shouldWait() {
|
||||
p.cond.Wait()
|
||||
}
|
||||
return p.v != nil && p.state == playerStatePlaying
|
||||
}
|
||||
|
||||
func (p *Player) writeImpl(buf []byte) {
|
||||
if p.state == playerStateClosed {
|
||||
return
|
||||
}
|
||||
if p.v == nil {
|
||||
return
|
||||
}
|
||||
p.v.Write(buf)
|
||||
}
|
||||
|
||||
func (p *Player) loop() {
|
||||
const readChunkSize = 4096
|
||||
|
||||
buf := make([]byte, readChunkSize)
|
||||
|
||||
for {
|
||||
if !p.waitUntilUnpaused() {
|
||||
return
|
||||
}
|
||||
|
||||
p.cond.L.Lock()
|
||||
n, err := p.src.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
p.setErrorImpl(err)
|
||||
p.cond.L.Unlock()
|
||||
return
|
||||
}
|
||||
if n > 0 {
|
||||
p.writeImpl(buf[:n])
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
p.closeImpl(false)
|
||||
p.cond.L.Unlock()
|
||||
return
|
||||
}
|
||||
p.cond.L.Unlock()
|
||||
}
|
||||
func (c *Context) bufferSize() int {
|
||||
return c.sampleRate * c.channelNum * c.bitDepthInBytes / 4 // 0.25[s]
|
||||
}
|
||||
|
400
audio/internal/cbackend/player.go
Normal file
400
audio/internal/cbackend/player.go
Normal file
@ -0,0 +1,400 @@
|
||||
// Copyright 2021 The Ebiten Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build ebitencbackend
|
||||
// +build ebitencbackend
|
||||
|
||||
// TODO: This implementation is very similar to github.com/hajimehoshi/oto/v2's player_notjs.go
|
||||
// Unify them if possible.
|
||||
|
||||
package cbackend
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type playerState int
|
||||
|
||||
const (
|
||||
playerPaused playerState = iota
|
||||
playerPlay
|
||||
playerClosed
|
||||
)
|
||||
|
||||
type players struct {
|
||||
players map[*playerImpl]struct{}
|
||||
buf []float32
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
func newPlayers() *players {
|
||||
p := &players{
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
}
|
||||
go p.loop()
|
||||
return p
|
||||
}
|
||||
|
||||
func (ps *players) shouldWait() bool {
|
||||
for p := range ps.players {
|
||||
if p.canReadSourceToBuffer() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ps *players) wait() {
|
||||
ps.cond.L.Lock()
|
||||
defer ps.cond.L.Unlock()
|
||||
|
||||
for ps.shouldWait() {
|
||||
ps.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *players) loop() {
|
||||
var players []*playerImpl
|
||||
for {
|
||||
ps.wait()
|
||||
|
||||
ps.cond.L.Lock()
|
||||
players = players[:0]
|
||||
for p := range ps.players {
|
||||
players = append(players, p)
|
||||
}
|
||||
ps.cond.L.Unlock()
|
||||
|
||||
for _, p := range players {
|
||||
p.readSourceToBuffer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *players) addPlayer(player *playerImpl) {
|
||||
ps.cond.L.Lock()
|
||||
defer ps.cond.L.Unlock()
|
||||
|
||||
if ps.players == nil {
|
||||
ps.players = map[*playerImpl]struct{}{}
|
||||
}
|
||||
ps.players[player] = struct{}{}
|
||||
ps.cond.Signal()
|
||||
}
|
||||
|
||||
func (ps *players) removePlayer(player *playerImpl) {
|
||||
ps.cond.L.Lock()
|
||||
defer ps.cond.L.Unlock()
|
||||
|
||||
delete(ps.players, player)
|
||||
ps.cond.Signal()
|
||||
}
|
||||
|
||||
func (ps *players) read(buf []float32) {
|
||||
ps.cond.L.Lock()
|
||||
players := make([]*playerImpl, 0, len(ps.players))
|
||||
for p := range ps.players {
|
||||
players = append(players, p)
|
||||
}
|
||||
ps.cond.L.Unlock()
|
||||
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
for _, p := range players {
|
||||
p.readBufferAndAdd(buf)
|
||||
}
|
||||
ps.cond.Signal()
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
p *playerImpl
|
||||
}
|
||||
|
||||
type playerImpl struct {
|
||||
context *Context
|
||||
src io.Reader
|
||||
volume float64
|
||||
err atomic.Value
|
||||
state playerState
|
||||
tmpbuf []byte
|
||||
buf []byte
|
||||
eof bool
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func newPlayer(context *Context, src io.Reader) *Player {
|
||||
p := &Player{
|
||||
p: &playerImpl{
|
||||
context: context,
|
||||
src: src,
|
||||
volume: 1,
|
||||
},
|
||||
}
|
||||
runtime.SetFinalizer(p, (*Player).Close)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Player) Err() error {
|
||||
return p.p.Err()
|
||||
}
|
||||
|
||||
func (p *playerImpl) Err() error {
|
||||
if err := p.err.Load(); err != nil {
|
||||
return err.(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Player) Play() {
|
||||
p.p.Play()
|
||||
}
|
||||
|
||||
func (p *playerImpl) Play() {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
close(ch)
|
||||
p.playImpl()
|
||||
}()
|
||||
<-ch
|
||||
}
|
||||
|
||||
func (p *playerImpl) ensureTmpBuf() []byte {
|
||||
if p.tmpbuf == nil {
|
||||
p.tmpbuf = make([]byte, p.context.bufferSize())
|
||||
}
|
||||
return p.tmpbuf
|
||||
}
|
||||
|
||||
func (p *playerImpl) playImpl() {
|
||||
if p.err.Load() != nil {
|
||||
return
|
||||
}
|
||||
if p.state != playerPaused {
|
||||
return
|
||||
}
|
||||
|
||||
if !p.eof {
|
||||
buf := p.ensureTmpBuf()
|
||||
for len(p.buf) < p.context.bufferSize() {
|
||||
n, err := p.src.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
p.setErrorImpl(err)
|
||||
return
|
||||
}
|
||||
p.buf = append(p.buf, buf[:n]...)
|
||||
if err == io.EOF {
|
||||
if len(p.buf) == 0 {
|
||||
p.eof = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !p.eof || len(p.buf) > 0 {
|
||||
p.state = playerPlay
|
||||
}
|
||||
|
||||
p.m.Unlock()
|
||||
p.context.players.addPlayer(p)
|
||||
p.m.Lock()
|
||||
}
|
||||
|
||||
func (p *Player) Pause() {
|
||||
p.p.Pause()
|
||||
}
|
||||
|
||||
func (p *playerImpl) Pause() {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.state != playerPlay {
|
||||
return
|
||||
}
|
||||
p.state = playerPaused
|
||||
}
|
||||
|
||||
func (p *Player) Reset() {
|
||||
p.p.Reset()
|
||||
}
|
||||
|
||||
func (p *playerImpl) Reset() {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
p.resetImpl()
|
||||
}
|
||||
|
||||
func (p *playerImpl) resetImpl() {
|
||||
if p.state == playerClosed {
|
||||
return
|
||||
}
|
||||
p.state = playerPaused
|
||||
p.buf = p.buf[:0]
|
||||
p.eof = false
|
||||
}
|
||||
|
||||
func (p *Player) IsPlaying() bool {
|
||||
return p.p.IsPlaying()
|
||||
}
|
||||
|
||||
func (p *playerImpl) IsPlaying() bool {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
return p.state == playerPlay
|
||||
}
|
||||
|
||||
func (p *Player) Volume() float64 {
|
||||
return p.p.Volume()
|
||||
}
|
||||
|
||||
func (p *playerImpl) Volume() float64 {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
return p.volume
|
||||
}
|
||||
|
||||
func (p *Player) SetVolume(volume float64) {
|
||||
p.p.SetVolume(volume)
|
||||
}
|
||||
|
||||
func (p *playerImpl) SetVolume(volume float64) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
p.volume = volume
|
||||
}
|
||||
|
||||
func (p *Player) UnplayedBufferSize() int {
|
||||
return p.p.UnplayedBufferSize()
|
||||
}
|
||||
|
||||
func (p *playerImpl) UnplayedBufferSize() int {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
return len(p.buf)
|
||||
}
|
||||
|
||||
func (p *Player) Close() error {
|
||||
runtime.SetFinalizer(p, nil)
|
||||
return p.p.Close()
|
||||
}
|
||||
|
||||
func (p *playerImpl) Close() error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
return p.closeImpl()
|
||||
}
|
||||
|
||||
func (p *playerImpl) closeImpl() error {
|
||||
p.m.Unlock()
|
||||
p.context.players.removePlayer(p)
|
||||
p.m.Lock()
|
||||
|
||||
if p.state == playerClosed {
|
||||
return nil
|
||||
}
|
||||
p.state = playerClosed
|
||||
p.buf = nil
|
||||
if err := p.err.Load(); err != nil {
|
||||
return err.(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *playerImpl) readBufferAndAdd(buf []float32) int {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.state != playerPlay {
|
||||
return 0
|
||||
}
|
||||
|
||||
bitDepthInBytes := p.context.bitDepthInBytes
|
||||
n := len(p.buf) / bitDepthInBytes
|
||||
if n > len(buf) {
|
||||
n = len(buf)
|
||||
}
|
||||
volume := float32(p.volume)
|
||||
src := p.buf[:n*bitDepthInBytes]
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
var v float32
|
||||
switch bitDepthInBytes {
|
||||
case 1:
|
||||
v8 := src[i]
|
||||
v = float32(v8-(1<<7)) / (1 << 7)
|
||||
case 2:
|
||||
v16 := int16(src[2*i]) | (int16(src[2*i+1]) << 8)
|
||||
v = float32(v16) / (1 << 15)
|
||||
}
|
||||
buf[i] += v * volume
|
||||
}
|
||||
|
||||
copy(p.buf, p.buf[n*bitDepthInBytes:])
|
||||
p.buf = p.buf[:len(p.buf)-n*bitDepthInBytes]
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *playerImpl) canReadSourceToBuffer() bool {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.eof {
|
||||
return false
|
||||
}
|
||||
return len(p.buf) < p.context.bufferSize()
|
||||
}
|
||||
|
||||
func (p *playerImpl) readSourceToBuffer() {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.err.Load() != nil {
|
||||
return
|
||||
}
|
||||
if p.state == playerClosed {
|
||||
return
|
||||
}
|
||||
|
||||
if len(p.buf) >= p.context.bufferSize() {
|
||||
return
|
||||
}
|
||||
|
||||
buf := p.ensureTmpBuf()
|
||||
n, err := p.src.Read(buf)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
p.setErrorImpl(err)
|
||||
return
|
||||
}
|
||||
|
||||
p.buf = append(p.buf, buf[:n]...)
|
||||
if err == io.EOF && len(p.buf) == 0 {
|
||||
p.state = playerPaused
|
||||
p.eof = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *playerImpl) setErrorImpl(err error) {
|
||||
p.err.Store(err)
|
||||
p.closeImpl()
|
||||
}
|
@ -51,29 +51,18 @@ package cbackend
|
||||
// void EbitenGetTouches(struct Touch* touches);
|
||||
//
|
||||
// // Audio
|
||||
// // TODO: Implement mixing on Go side and reduce the API.
|
||||
// typedef void (*OnWrittenCallback)(int id);
|
||||
// void EbitenOpenAudio(int sample_rate, int channel_num, int bit_depth_in_bytes);
|
||||
// typedef void (*OnReadCallback)(float* buf, size_t length);
|
||||
// void EbitenOpenAudio(int sample_rate, int channel_num, OnReadCallback on_read_callback);
|
||||
// void EbitenCloseAudio();
|
||||
// int EbitenCreateAudioPlayer(OnWrittenCallback on_written_callback);
|
||||
// void EbitenAudioPlayerPlay(int id);
|
||||
// void EbitenAudioPlayerPause(int id);
|
||||
// void EbitenAudioPlayerWrite(int id, uint8_t* data, int length);
|
||||
// void EbitenAudioPlayerClose(int id, int immediately);
|
||||
// double EbitenAudioPlayerGetVolume(int id);
|
||||
// void EbitenAudioPlayerSetVolume(int id, double volume);
|
||||
// int EbitenAudioPlayerGetUnplayedBufferSize(int id);
|
||||
// float EbitenAudioBufferSizeInSeconds();
|
||||
//
|
||||
// void EbitenAudioPlayerOnWrittenCallback(int id);
|
||||
// static int EbitenCreateAudioPlayerProxy() {
|
||||
// return EbitenCreateAudioPlayer(EbitenAudioPlayerOnWrittenCallback);
|
||||
// void EbitenAudioOnReadCallback(float* buf, size_t length);
|
||||
// static void EbitenOpenAudioProxy(int sample_rate, int channel_num) {
|
||||
// EbitenOpenAudio(sample_rate, channel_num, EbitenAudioOnReadCallback);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||
@ -170,80 +159,23 @@ func AppendTouches(touches []Touch) []Touch {
|
||||
return touches
|
||||
}
|
||||
|
||||
func OpenAudio(sampleRate, channelNum, bitDepthInBytes int) {
|
||||
C.EbitenOpenAudio(C.int(sampleRate), C.int(channelNum), C.int(bitDepthInBytes))
|
||||
var onReadCallback func(buf []float32)
|
||||
|
||||
func OpenAudio(sampleRate, channelNum int, onRead func(buf []float32)) {
|
||||
C.EbitenOpenAudioProxy(C.int(sampleRate), C.int(channelNum))
|
||||
onReadCallback = onRead
|
||||
}
|
||||
|
||||
func CloseAudio() {
|
||||
C.EbitenCloseAudio()
|
||||
}
|
||||
|
||||
func CreateAudioPlayer(onWritten func()) *AudioPlayer {
|
||||
id := C.EbitenCreateAudioPlayerProxy()
|
||||
p := &AudioPlayer{
|
||||
id: id,
|
||||
}
|
||||
onWrittenCallbacksM.Lock()
|
||||
defer onWrittenCallbacksM.Unlock()
|
||||
onWrittenCallbacks[id] = onWritten
|
||||
return p
|
||||
}
|
||||
|
||||
func AudioBufferSizeInSeconds() float64 {
|
||||
return float64(C.EbitenAudioBufferSizeInSeconds())
|
||||
}
|
||||
|
||||
type AudioPlayer struct {
|
||||
id C.int
|
||||
}
|
||||
|
||||
func (p *AudioPlayer) Play() {
|
||||
C.EbitenAudioPlayerPlay(p.id)
|
||||
}
|
||||
|
||||
func (p *AudioPlayer) Pause() {
|
||||
C.EbitenAudioPlayerPause(p.id)
|
||||
}
|
||||
|
||||
func (p *AudioPlayer) Write(buf []byte) {
|
||||
C.EbitenAudioPlayerWrite(p.id, (*C.uint8_t)(unsafe.Pointer(&buf[0])), C.int(len(buf)))
|
||||
runtime.KeepAlive(buf)
|
||||
}
|
||||
|
||||
func (p *AudioPlayer) Close(immediately bool) {
|
||||
var i C.int
|
||||
if immediately {
|
||||
i = 1
|
||||
}
|
||||
C.EbitenAudioPlayerClose(p.id, i)
|
||||
|
||||
onWrittenCallbacksM.Lock()
|
||||
defer onWrittenCallbacksM.Unlock()
|
||||
delete(onWrittenCallbacks, p.id)
|
||||
}
|
||||
|
||||
func (p *AudioPlayer) Volume() float64 {
|
||||
return float64(C.EbitenAudioPlayerGetVolume(p.id))
|
||||
}
|
||||
|
||||
func (p *AudioPlayer) SetVolume(volume float64) {
|
||||
C.EbitenAudioPlayerSetVolume(p.id, C.double(volume))
|
||||
}
|
||||
|
||||
func (p *AudioPlayer) UnplayedBufferSize() int {
|
||||
return int(C.EbitenAudioPlayerGetUnplayedBufferSize(p.id))
|
||||
}
|
||||
|
||||
var (
|
||||
onWrittenCallbacks = map[C.int]func(){}
|
||||
onWrittenCallbacksM sync.Mutex
|
||||
)
|
||||
|
||||
//export EbitenAudioPlayerOnWrittenCallback
|
||||
func EbitenAudioPlayerOnWrittenCallback(id C.int) {
|
||||
onWrittenCallbacksM.Lock()
|
||||
defer onWrittenCallbacksM.Unlock()
|
||||
if c, ok := onWrittenCallbacks[id]; ok {
|
||||
c()
|
||||
}
|
||||
//export EbitenAudioOnReadCallback
|
||||
func EbitenAudioOnReadCallback(buf *C.float, length C.size_t) {
|
||||
var s []float32
|
||||
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
|
||||
h.Data = uintptr(unsafe.Pointer(buf))
|
||||
h.Len = int(length)
|
||||
h.Cap = int(length)
|
||||
onReadCallback(s)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user