mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
100 lines
2.2 KiB
Go
100 lines
2.2 KiB
Go
package oto
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
|
|
"github.com/ebitengine/oto/v3"
|
|
"github.com/vsariola/sointu"
|
|
)
|
|
|
|
const latency = 2048 // in samples at 44100 Hz = ~46 ms
|
|
|
|
type (
|
|
OtoContext oto.Context
|
|
|
|
OtoPlayer struct {
|
|
player *oto.Player
|
|
reader *OtoReader
|
|
}
|
|
|
|
OtoReader struct {
|
|
audioSource sointu.AudioSource
|
|
tmpBuffer sointu.AudioBuffer
|
|
waitGroup sync.WaitGroup
|
|
err error
|
|
errMutex sync.RWMutex
|
|
}
|
|
)
|
|
|
|
func NewContext() (*OtoContext, error) {
|
|
op := oto.NewContextOptions{}
|
|
op.SampleRate = 44100
|
|
op.ChannelCount = 2
|
|
op.Format = oto.FormatFloat32LE
|
|
context, readyChan, err := oto.NewContext(&op)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create oto context: %w", err)
|
|
}
|
|
<-readyChan
|
|
return (*OtoContext)(context), nil
|
|
}
|
|
|
|
func (c *OtoContext) Play(r sointu.AudioSource) sointu.CloserWaiter {
|
|
reader := &OtoReader{audioSource: r}
|
|
reader.waitGroup.Add(1)
|
|
player := (*oto.Context)(c).NewPlayer(reader)
|
|
player.SetBufferSize(latency * 8)
|
|
player.Play()
|
|
return OtoPlayer{player: player, reader: reader}
|
|
}
|
|
|
|
func (o OtoPlayer) Wait() {
|
|
o.reader.waitGroup.Wait()
|
|
}
|
|
|
|
func (o OtoPlayer) Close() error {
|
|
o.reader.closeWithError(errors.New("OtoPlayer was closed"))
|
|
return o.player.Close()
|
|
}
|
|
|
|
func (o *OtoReader) Read(b []byte) (n int, err error) {
|
|
o.errMutex.RLock()
|
|
if o.err != nil {
|
|
o.errMutex.RUnlock()
|
|
return 0, o.err
|
|
}
|
|
o.errMutex.RUnlock()
|
|
if len(b)%8 != 0 {
|
|
return o.closeWithError(fmt.Errorf("oto: Read buffer length must be a multiple of 8"))
|
|
}
|
|
samples := len(b) / 8
|
|
if samples > len(o.tmpBuffer) {
|
|
o.tmpBuffer = append(o.tmpBuffer, make(sointu.AudioBuffer, samples-len(o.tmpBuffer))...)
|
|
} else if samples < len(o.tmpBuffer) {
|
|
o.tmpBuffer = o.tmpBuffer[:samples]
|
|
}
|
|
err = o.audioSource(o.tmpBuffer)
|
|
if err != nil {
|
|
return o.closeWithError(err)
|
|
}
|
|
for i := range o.tmpBuffer {
|
|
binary.LittleEndian.PutUint32(b[i*8:], math.Float32bits(o.tmpBuffer[i][0]))
|
|
binary.LittleEndian.PutUint32(b[i*8+4:], math.Float32bits(o.tmpBuffer[i][1]))
|
|
}
|
|
return samples * 8, nil
|
|
}
|
|
|
|
func (o *OtoReader) closeWithError(err error) (int, error) {
|
|
o.errMutex.Lock()
|
|
defer o.errMutex.Unlock()
|
|
if o.err == nil {
|
|
o.err = err
|
|
o.waitGroup.Done()
|
|
}
|
|
return 0, err
|
|
}
|