mirror of
https://github.com/bloeys/wavy.git
synced 2025-12-29 09:28:19 +00:00
Use SoundBuffer for memory sounds
This commit is contained in:
70
sound_buffer.go
Executable file
70
sound_buffer.go
Executable file
@ -0,0 +1,70 @@
|
||||
package wavy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
//Pre-defined errors
|
||||
var (
|
||||
ErrInvalidWhence = errors.New("invalid whence value. Must be: io.SeekStart, io.SeekCurrent, or io.SeekEnd")
|
||||
ErrNegativeSeekPos = errors.New("negative seeker position")
|
||||
)
|
||||
|
||||
var _ io.ReadSeeker = &SoundBuffer{}
|
||||
|
||||
type SoundBuffer struct {
|
||||
Data []byte
|
||||
|
||||
//Pos is the starting position of the next read
|
||||
Pos int64
|
||||
}
|
||||
|
||||
//Read only returns io.EOF when no more input is available
|
||||
func (sb *SoundBuffer) Read(outBuf []byte) (bytesRead int, err error) {
|
||||
|
||||
bytesRead = copy(outBuf, sb.Data[sb.Pos:])
|
||||
if bytesRead == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
sb.Pos += int64(bytesRead)
|
||||
return bytesRead, nil
|
||||
}
|
||||
|
||||
//Seek returns the new position.
|
||||
//An error is only returned if the whence is invalid or if the resulting position is negative.
|
||||
//
|
||||
//If the resulting position is >=len(SoundBuffer.Data) then future Read() calls will return io.EOF
|
||||
func (sb *SoundBuffer) Seek(offset int64, whence int) (int64, error) {
|
||||
|
||||
newPos := sb.Pos
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
newPos = offset
|
||||
case io.SeekCurrent:
|
||||
newPos += offset
|
||||
case io.SeekEnd:
|
||||
newPos = int64(len(sb.Data)) + offset
|
||||
default:
|
||||
return 0, ErrInvalidWhence
|
||||
}
|
||||
|
||||
if newPos < 0 {
|
||||
return 0, ErrNegativeSeekPos
|
||||
}
|
||||
|
||||
sb.Pos = newPos
|
||||
return sb.Pos, nil
|
||||
}
|
||||
|
||||
//Clone returns a new SoundBuffer that uses the same SoundBuffer.Data but with an independent ReadSeeker.
|
||||
//Allows you to have many readers all reading from different positions of the same buffer.
|
||||
//
|
||||
//The new buffer starts reading from the start (Pos=0)
|
||||
func (sb *SoundBuffer) Clone() SoundBuffer {
|
||||
return SoundBuffer{
|
||||
Data: sb.Data,
|
||||
Pos: 0,
|
||||
}
|
||||
}
|
||||
36
sound_enums.go
Executable file
36
sound_enums.go
Executable file
@ -0,0 +1,36 @@
|
||||
package wavy
|
||||
|
||||
type SoundType int
|
||||
|
||||
const (
|
||||
SoundType_Unknown SoundType = iota
|
||||
SoundType_MP3
|
||||
)
|
||||
|
||||
type SampleRate int
|
||||
|
||||
const (
|
||||
SampleRate_44100 SampleRate = 44100
|
||||
SampleRate_48000 SampleRate = 48000
|
||||
)
|
||||
|
||||
type SoundChannelCount int
|
||||
|
||||
const (
|
||||
SoundChannelCount_1 SoundChannelCount = 1
|
||||
SoundChannelCount_2 SoundChannelCount = 2
|
||||
)
|
||||
|
||||
type SoundBitDepth int
|
||||
|
||||
const (
|
||||
SoundBitDepth_1 SoundBitDepth = 1
|
||||
SoundBitDepth_2 SoundBitDepth = 2
|
||||
)
|
||||
|
||||
type SoundMode int
|
||||
|
||||
const (
|
||||
SoundMode_Streaming SoundMode = iota
|
||||
SoundMode_Memory
|
||||
)
|
||||
129
wavy.go
129
wavy.go
@ -14,49 +14,40 @@ import (
|
||||
"github.com/hajimehoshi/oto/v2"
|
||||
)
|
||||
|
||||
type SoundType int
|
||||
//SoundInfo contains static info about a loaded sound file
|
||||
type SoundInfo struct {
|
||||
Type SoundType
|
||||
Mode SoundMode
|
||||
|
||||
const (
|
||||
SoundType_Unknown SoundType = iota
|
||||
SoundType_MP3
|
||||
)
|
||||
//Size is the sound's size in bytes
|
||||
Size int64
|
||||
}
|
||||
|
||||
type SampleRate int
|
||||
type Sound struct {
|
||||
Player oto.Player
|
||||
|
||||
const (
|
||||
SampleRate_44100 SampleRate = 44100
|
||||
SampleRate_48000 SampleRate = 48000
|
||||
)
|
||||
//FileDesc is the file descriptor of the sound file being streamed.
|
||||
//This is only set if sound is streamed, and is kept to ensure GC doesn't hit it
|
||||
FileDesc *os.File
|
||||
|
||||
type SoundChannelCount int
|
||||
//Data is an io.ReadSeeker over an open file or over a buffer containing the uncompressed sound file.
|
||||
//Becomes nil after close
|
||||
Data io.ReadSeeker
|
||||
|
||||
const (
|
||||
SoundChannelCount_1 SoundChannelCount = 1
|
||||
SoundChannelCount_2 SoundChannelCount = 2
|
||||
)
|
||||
|
||||
type SoundBitDepth int
|
||||
|
||||
const (
|
||||
SoundBitDepth_1 SoundBitDepth = 1
|
||||
SoundBitDepth_2 SoundBitDepth = 2
|
||||
)
|
||||
|
||||
type SoundMode int
|
||||
|
||||
const (
|
||||
SoundMode_Streaming SoundMode = iota
|
||||
SoundMode_Memory
|
||||
)
|
||||
Info SoundInfo
|
||||
}
|
||||
|
||||
//Those values are set after Init
|
||||
var (
|
||||
Ctx *oto.Context
|
||||
SamplingRate SampleRate
|
||||
ChanCount SoundChannelCount
|
||||
BitDepth SoundBitDepth
|
||||
)
|
||||
|
||||
//Pre-defined errors
|
||||
ErrunknownSoundType = errors.New("unknown sound type. Sound file extensions must be: .mp3")
|
||||
//Pre-defined errors
|
||||
var (
|
||||
ErrunknownSoundType = errors.New("unknown sound type. Sound file extension must be one of: .mp3")
|
||||
)
|
||||
|
||||
//Init prepares the default audio device and does any required setup.
|
||||
@ -77,29 +68,6 @@ func Init(sr SampleRate, chanCount SoundChannelCount, bitDepth SoundBitDepth) er
|
||||
return nil
|
||||
}
|
||||
|
||||
//SoundInfo contains static info about a loaded sound file
|
||||
type SoundInfo struct {
|
||||
Type SoundType
|
||||
Mode SoundMode
|
||||
|
||||
//Size is the sound's size in bytes
|
||||
Size int64
|
||||
}
|
||||
|
||||
type Sound struct {
|
||||
Player oto.Player
|
||||
|
||||
//FileDesc is the file descriptor of the sound file being streamed.
|
||||
//This is only set if sound is streamed, and is kept to ensure GC doesn't hit it
|
||||
FileDesc *os.File
|
||||
|
||||
//Bytes is an io.ReadSeeker over an open file or over a buffer containing the uncompressed sound file.
|
||||
//Becomes nil after close
|
||||
Bytes io.ReadSeeker
|
||||
|
||||
Info SoundInfo
|
||||
}
|
||||
|
||||
//PlayAsync plays the sound in the background and returns
|
||||
func (s *Sound) PlayAsync() {
|
||||
s.Player.Play()
|
||||
@ -135,7 +103,7 @@ func (s *Sound) RemainingTime() time.Duration {
|
||||
}
|
||||
|
||||
var currBytePos int64
|
||||
currBytePos, _ = s.Bytes.Seek(0, io.SeekCurrent)
|
||||
currBytePos, _ = s.Data.Seek(0, io.SeekCurrent)
|
||||
currBytePos -= int64(s.Player.UnplayedBufferSize())
|
||||
|
||||
lenInMS := float64(s.Info.Size-currBytePos) / float64(SamplingRate) / 4 * 1000
|
||||
@ -143,7 +111,7 @@ func (s *Sound) RemainingTime() time.Duration {
|
||||
}
|
||||
|
||||
func (s *Sound) IsClosed() bool {
|
||||
return s.Bytes == nil
|
||||
return s.Data == nil
|
||||
}
|
||||
|
||||
//Close will clean underlying resources, and the 'Ctx' and 'Bytes' fields will be made nil.
|
||||
@ -159,7 +127,7 @@ func (s *Sound) Close() error {
|
||||
fdErr = s.FileDesc.Close()
|
||||
}
|
||||
|
||||
s.Bytes = nil
|
||||
s.Data = nil
|
||||
playerErr := s.Player.Close()
|
||||
|
||||
if playerErr == nil && fdErr == nil {
|
||||
@ -214,7 +182,7 @@ func NewSoundStreaming(fpath string) (s *Sound, err error) {
|
||||
|
||||
s.Info.Size = dec.Length()
|
||||
s.Player = Ctx.NewPlayer(dec)
|
||||
s.Bytes = dec
|
||||
s.Data = dec
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@ -254,9 +222,15 @@ func NewSoundMem(fpath string) (s *Sound, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.Bytes = dec
|
||||
s.Info.Size = dec.Length()
|
||||
s.Player = Ctx.NewPlayer(dec)
|
||||
finalBuf, err := ReadAllFromReader(dec, 0, uint64(dec.Length()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sb := &SoundBuffer{Data: finalBuf}
|
||||
s.Data = sb
|
||||
s.Player = Ctx.NewPlayer(sb)
|
||||
s.Info.Size = int64(len(sb.Data))
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@ -272,3 +246,36 @@ func GetSoundFileType(fpath string) SoundType {
|
||||
return SoundType_Unknown
|
||||
}
|
||||
}
|
||||
|
||||
//ReadAllFromReader takes an io.Reader and reads until error or io.EOF.
|
||||
//
|
||||
//If io.EOF is reached then read bytes are returned with a nil error.
|
||||
//If the reader returns an error that's not io.EOF then everything read till that point is returned along with the error
|
||||
//
|
||||
//readingBufSize is the buffer used to read from reader.Read(). Bigger values might read more efficiently.
|
||||
//If readingBufSize<4096 then readingBufSize is set to 4096
|
||||
//
|
||||
//ouputBufSize is used to set the capacity of the final buffer to be returned. This can greatly improve performance
|
||||
//if you know the size of the output. It is allowed to have an outputBufSize that's smaller or larger than what the reader
|
||||
//ends up returning
|
||||
func ReadAllFromReader(reader io.Reader, readingBufSize, ouputBufSize uint64) ([]byte, error) {
|
||||
|
||||
if readingBufSize < 4096 {
|
||||
readingBufSize = 4096
|
||||
}
|
||||
|
||||
tempBuf := make([]byte, readingBufSize)
|
||||
finalBuf := make([]byte, 0, ouputBufSize)
|
||||
for {
|
||||
|
||||
readBytesCount, err := reader.Read(tempBuf)
|
||||
finalBuf = append(finalBuf, tempBuf[:readBytesCount]...)
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return finalBuf, nil
|
||||
}
|
||||
return finalBuf, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,8 @@ func TestSound(t *testing.T) {
|
||||
}
|
||||
s.PlaySync()
|
||||
|
||||
//Test repeat playing
|
||||
s.Player.Reset()
|
||||
s.Bytes.Seek(0, io.SeekStart)
|
||||
s.Data.Seek(0, io.SeekStart)
|
||||
s.PlaySync()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user