all: add float32 version of audio APIs

This change adds these APIs

 * (*audio.Context).NewPlayerF32
 * (*audio.Context).NewPlayerF32FromBytes
 * audio.NewInfiniteLoopF32
 * audio.NewInfiniteLoopWithIntroF32
 * audio.ResampleF32
 * mp3.DecodeF32
 * vorbis.DecodeF32
 * wav.DecodeF32

Closes #2160
This commit is contained in:
Hajime Hoshi 2024-07-14 22:03:47 +09:00
parent 1d7c350967
commit f0ef1ecad0
13 changed files with 386 additions and 53 deletions

View File

@ -350,6 +350,36 @@ func (c *Context) NewPlayer(src io.Reader) (*Player, error) {
return p, nil return p, nil
} }
// NewPlayerF32 creates a new player with the given stream.
//
// src's format must be linear PCM (32bit float, little endian, 2 channel stereo)
// without a header (e.g. RIFF header).
// The sample rate must be same as that of the audio context.
//
// The player is seekable when src is io.Seeker.
// Attempt to seek the player that is not io.Seeker causes panic.
//
// Note that the given src can't be shared with other Player objects.
//
// NewPlayer tries to call Seek of src to get the current position.
// NewPlayer returns error when the Seek returns error.
//
// A Player doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func (c *Context) NewPlayerF32(src io.Reader) (*Player, error) {
_, seekable := src.(io.Seeker)
pi, err := c.playerFactory.newPlayer(c, src, seekable, src, bitDepthInBytesFloat32)
if err != nil {
return nil, err
}
p := &Player{pi}
runtime.SetFinalizer(p, (*Player).finalize)
return p, nil
}
// NewPlayer creates a new player with the given stream. // NewPlayer creates a new player with the given stream.
// //
// Deprecated: as of v2.2. Use (*Context).NewPlayer instead. // Deprecated: as of v2.2. Use (*Context).NewPlayer instead.
@ -372,6 +402,21 @@ func (c *Context) NewPlayerFromBytes(src []byte) *Player {
return p return p
} }
// NewPlayerF32FromBytes creates a new player with the given bytes.
//
// As opposed to NewPlayerF32, you don't have to care if src is already used by another player or not.
// src can be shared by multiple players.
//
// The format of src should be same as noted at NewPlayerF32.
func (c *Context) NewPlayerF32FromBytes(src []byte) *Player {
p, err := c.NewPlayerF32(bytes.NewReader(src))
if err != nil {
// Errors should never happen.
panic(fmt.Sprintf("audio: %v at NewPlayerFromBytesF32", err))
}
return p
}
// NewPlayerFromBytes creates a new player with the given bytes. // NewPlayerFromBytes creates a new player with the given bytes.
// //
// Deprecated: as of v2.2. Use (*Context).NewPlayerFromBytes instead. // Deprecated: as of v2.2. Use (*Context).NewPlayerFromBytes instead.
@ -499,7 +544,7 @@ func (h *hookerImpl) AppendHookOnBeforeUpdate(f func() error) {
hook.AppendHookOnBeforeUpdate(f) hook.AppendHookOnBeforeUpdate(f)
} }
// Resample converts the sample rate of the given stream. // Resample converts the sample rate of the given singed 16bit integer, little-endian, 2 channels (stereo) stream.
// size is the length of the source stream in bytes. // size is the length of the source stream in bytes.
// from is the original sample rate. // from is the original sample rate.
// to is the target sample rate. // to is the target sample rate.
@ -511,3 +556,16 @@ func Resample(source io.ReadSeeker, size int64, from, to int) io.ReadSeeker {
} }
return convert.NewResampling(source, size, from, to, bitDepthInBytesInt16) return convert.NewResampling(source, size, from, to, bitDepthInBytesInt16)
} }
// ResampleF32 converts the sample rate of the given 32bit float, little-endian, 2 channels (stereo) stream.
// size is the length of the source stream in bytes.
// from is the original sample rate.
// to is the target sample rate.
//
// If the original sample rate equals to the new one, Resample returns source as it is.
func ResampleF32(source io.ReadSeeker, size int64, from, to int) io.ReadSeeker {
if from == to {
return source
}
return convert.NewResampling(source, size, from, to, bitDepthInBytesFloat32)
}

View File

@ -24,6 +24,10 @@ func NewFloat32BytesReaderFromInt16BytesReader(r io.Reader) io.Reader {
return &float32BytesReader{r: r} return &float32BytesReader{r: r}
} }
func NewFloat32BytesReadSeekerFromInt16BytesReadSeeker(r io.ReadSeeker) io.ReadSeeker {
return &float32BytesReader{r: r}
}
type float32BytesReader struct { type float32BytesReader struct {
r io.Reader r io.Reader
eof bool eof bool

View File

@ -17,6 +17,7 @@ package audio
import ( import (
"fmt" "fmt"
"io" "io"
"math"
) )
// InfiniteLoop represents a looped stream which never ends. // InfiniteLoop represents a looped stream which never ends.
@ -42,18 +43,35 @@ type InfiniteLoop struct {
// NewInfiniteLoop creates a new infinite loop stream with a source stream and length in bytes. // NewInfiniteLoop creates a new infinite loop stream with a source stream and length in bytes.
// //
// src is a signed 16bit integer little endian stream, 2 channels (stereo).
//
// If the loop's total length is exactly the same as src's length, you might hear noises around the loop joint. // If the loop's total length is exactly the same as src's length, you might hear noises around the loop joint.
// This noise can be heard especially when src is decoded from a lossy compression format like Ogg/Vorbis and MP3. // This noise can be heard especially when src is decoded from a lossy compression format like Ogg/Vorbis and MP3.
// In this case, try to add more (about 0.1[s]) data to src after the loop end. // In this case, try to add more (about 0.1[s]) data to src after the loop end.
// If src has data after the loop end, an InfiniteLoop uses part of the data to blend with the loop start // If src has data after the loop end, an InfiniteLoop uses part of the data to blend with the loop start
// to make the loop joint smooth. // to make the loop joint smooth.
func NewInfiniteLoop(src io.ReadSeeker, length int64) *InfiniteLoop { func NewInfiniteLoop(src io.ReadSeeker, length int64) *InfiniteLoop {
return NewInfiniteLoopWithIntro(src, 0, length) return newInfiniteLoopWithIntro(src, 0, length, bitDepthInBytesInt16)
}
// NewInfiniteLoopF32 creates a new infinite loop stream with a source stream and length in bytes.
//
// src is a 32bit float little endian stream, 2 channels (stereo).
//
// If the loop's total length is exactly the same as src's length, you might hear noises around the loop joint.
// This noise can be heard especially when src is decoded from a lossy compression format like Ogg/Vorbis and MP3.
// In this case, try to add more (about 0.1[s]) data to src after the loop end.
// If src has data after the loop end, an InfiniteLoop uses part of the data to blend with the loop start
// to make the loop joint smooth.
func NewInfiniteLoopF32(src io.ReadSeeker, length int64) *InfiniteLoop {
return newInfiniteLoopWithIntro(src, 0, length, bitDepthInBytesFloat32)
} }
// NewInfiniteLoopWithIntro creates a new infinite loop stream with an intro part. // NewInfiniteLoopWithIntro creates a new infinite loop stream with an intro part.
// NewInfiniteLoopWithIntro accepts a source stream src, introLength in bytes and loopLength in bytes. // NewInfiniteLoopWithIntro accepts a source stream src, introLength in bytes and loopLength in bytes.
// //
// src is a signed 16bit integer little endian stream, 2 channels (stereo).
//
// If the loop's total length is exactly the same as src's length, you might hear noises around the loop joint. // If the loop's total length is exactly the same as src's length, you might hear noises around the loop joint.
// This noise can be heard especially when src is decoded from a lossy compression format like Ogg/Vorbis and MP3. // This noise can be heard especially when src is decoded from a lossy compression format like Ogg/Vorbis and MP3.
// In this case, try to add more (about 0.1[s]) data to src after the loop end. // In this case, try to add more (about 0.1[s]) data to src after the loop end.
@ -63,6 +81,20 @@ func NewInfiniteLoopWithIntro(src io.ReadSeeker, introLength int64, loopLength i
return newInfiniteLoopWithIntro(src, introLength, loopLength, bitDepthInBytesInt16) return newInfiniteLoopWithIntro(src, introLength, loopLength, bitDepthInBytesInt16)
} }
// NewInfiniteLoopWithIntroF32 creates a new infinite loop stream with an intro part.
// NewInfiniteLoopWithIntroF32 accepts a source stream src, introLength in bytes and loopLength in bytes.
//
// src is a 32bit float little endian stream, 2 channels (stereo).
//
// If the loop's total length is exactly the same as src's length, you might hear noises around the loop joint.
// This noise can be heard especially when src is decoded from a lossy compression format like Ogg/Vorbis and MP3.
// In this case, try to add more (about 0.1[s]) data to src after the loop end.
// If src has data after the loop end, an InfiniteLoop uses part of the data to blend with the loop start
// to make the loop joint smooth.
func NewInfiniteLoopWithIntroF32(src io.ReadSeeker, introLength int64, loopLength int64) *InfiniteLoop {
return newInfiniteLoopWithIntro(src, introLength, loopLength, bitDepthInBytesFloat32)
}
func newInfiniteLoopWithIntro(src io.ReadSeeker, introLength int64, loopLength int64, bitDepthInBytes int) *InfiniteLoop { func newInfiniteLoopWithIntro(src io.ReadSeeker, introLength int64, loopLength int64, bitDepthInBytes int) *InfiniteLoop {
bytesPerSample := bitDepthInBytes * channelCount bytesPerSample := bitDepthInBytes * channelCount
return &InfiniteLoop{ return &InfiniteLoop{
@ -152,9 +184,18 @@ func (i *InfiniteLoop) Read(b []byte) (int, error) {
case 2: case 2:
afterLoop := int16(i.afterLoop[relpos]) | (int16(i.afterLoop[relpos+1]) << 8) afterLoop := int16(i.afterLoop[relpos]) | (int16(i.afterLoop[relpos+1]) << 8)
orig := int16(b[2*idx]) | (int16(b[2*idx+1]) << 8) orig := int16(b[2*idx]) | (int16(b[2*idx+1]) << 8)
newval := int16(float32(afterLoop)*rate + float32(orig)*(1-rate)) newVal := int16(float32(afterLoop)*rate + float32(orig)*(1-rate))
b[2*idx] = byte(newval) b[2*idx] = byte(newVal)
b[2*idx+1] = byte(newval >> 8) b[2*idx+1] = byte(newVal >> 8)
case 4:
afterLoop := math.Float32frombits(uint32(i.afterLoop[relpos]) | (uint32(i.afterLoop[relpos+1]) << 8) | (uint32(i.afterLoop[relpos+2]) << 16) | (uint32(i.afterLoop[relpos+3]) << 24))
orig := math.Float32frombits(uint32(b[4*idx]) | (uint32(b[4*idx+1]) << 8) | (uint32(b[4*idx+2]) << 16) | (uint32(b[4*idx+3]) << 24))
newVal := float32(afterLoop*rate + orig*(1-rate))
newValBits := math.Float32bits(newVal)
b[4*idx] = byte(newValBits)
b[4*idx+1] = byte(newValBits >> 8)
b[4*idx+2] = byte(newValBits >> 16)
b[4*idx+3] = byte(newValBits >> 24)
default: default:
panic("not reached") panic("not reached")
} }

View File

@ -29,6 +29,7 @@ import (
const ( const (
bitDepthInBytesInt16 = 2 bitDepthInBytesInt16 = 2
bitDepthInBytesFloat32 = 4
) )
// Stream is a decoded stream. // Stream is a decoded stream.
@ -58,7 +59,29 @@ func (s *Stream) SampleRate() int {
return s.sampleRate return s.sampleRate
} }
// DecodeWithoutResampling decodes an MP3 source and returns a decoded stream. // DecodeF32 decodes an MP3 source and returns a decoded stream in 32bit float, little endian, 2 channels (stereo) format.
//
// DecodeF32 returns error when decoding fails or IO error happens.
//
// The returned Stream's Seek is available only when src is an io.Seeker.
//
// A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func DecodeF32(src io.Reader) (*Stream, error) {
d, err := mp3.NewDecoder(src)
if err != nil {
return nil, err
}
r := convert.NewFloat32BytesReadSeekerFromInt16BytesReadSeeker(d)
s := &Stream{
readSeeker: r,
length: d.Length() / bitDepthInBytesInt16 * bitDepthInBytesFloat32,
sampleRate: d.SampleRate(),
}
return s, nil
}
// DecodeWithoutResampling decodes an MP3 source and returns a decoded stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// DecodeWithoutResampling returns error when decoding fails or IO error happens. // DecodeWithoutResampling returns error when decoding fails or IO error happens.
// //
@ -79,7 +102,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
return s, nil return s, nil
} }
// DecodeWithSampleRate decodes an MP3 source and returns a decoded stream. // DecodeWithSampleRate decodes an MP3 source and returns a decoded stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// DecodeWithSampleRate returns error when decoding fails or IO error happens. // DecodeWithSampleRate returns error when decoding fails or IO error happens.
// //
@ -113,7 +136,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
return s, nil return s, nil
} }
// Decode decodes MP3 source and returns a decoded stream. // Decode decodes MP3 source and returns a decoded stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// Decode returns error when decoding fails or IO error happens. // Decode returns error when decoding fails or IO error happens.
// //

80
audio/vorbis/float32.go Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2024 The Ebitengine 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.
package vorbis
import (
"io"
"math"
"github.com/jfreymuth/oggvorbis"
)
var _ io.ReadSeeker = (*float32BytesReadSeeker)(nil)
func newFloat32BytesReadSeeker(r *oggvorbis.Reader) *float32BytesReadSeeker {
return &float32BytesReadSeeker{r: r}
}
type float32BytesReadSeeker struct {
r *oggvorbis.Reader
fbuf []float32
pos int64
}
func (r *float32BytesReadSeeker) Read(buf []byte) (int, error) {
if len(buf) == 0 {
return 0, nil
}
l := max(len(buf)/4/r.r.Channels()*r.r.Channels(), 1)
if cap(r.fbuf) < l {
r.fbuf = make([]float32, l)
}
n, err := r.r.Read(r.fbuf[:l])
if err != nil && err != io.EOF {
return 0, err
}
for i := 0; i < n; i++ {
v := math.Float32bits(r.fbuf[i])
buf[4*i] = byte(v)
buf[4*i+1] = byte(v >> 8)
buf[4*i+2] = byte(v >> 16)
buf[4*i+3] = byte(v >> 24)
}
r.pos += int64(n * 4)
return n * 4, err
}
func (r *float32BytesReadSeeker) Seek(offset int64, whence int) (int64, error) {
sampleSize := int64(r.r.Channels()) * 4
offset = offset / sampleSize * sampleSize
switch whence {
case io.SeekStart:
case io.SeekCurrent:
offset += r.pos
case io.SeekEnd:
offset += r.r.Length()
}
r.pos = offset
if err := r.r.SetPosition(r.pos / sampleSize); err != nil {
return 0, err
}
return r.pos, nil
}

View File

@ -27,6 +27,7 @@ import (
const ( const (
bitDepthInBytesInt16 = 2 bitDepthInBytesInt16 = 2
bitDepthInBytesFloat32 = 4
) )
// Stream is a decoded audio stream. // Stream is a decoded audio stream.
@ -60,6 +61,47 @@ func (s *Stream) SampleRate() int {
return s.sampleRate return s.sampleRate
} }
// DecodeF32 decodes Ogg/Vorbis data to playable stream in 32bit float, little endian, 2 channels (stereo) format.
//
// DecodeF32 returns error when decoding fails or IO error happens.
//
// The returned Stream's Seek is available only when src is an io.Seeker.
//
// A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func DecodeF32(src io.Reader) (*Stream, error) {
r, err := oggvorbis.NewReader(src)
if err != nil {
return nil, err
}
if r.Channels() != 1 && r.Channels() != 2 {
return nil, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", r.Channels())
}
var s io.ReadSeeker = newFloat32BytesReadSeeker(r)
length := r.Length() * int64(r.Channels()) * bitDepthInBytesFloat32
if r.Channels() == 1 {
s = convert.NewStereoF32(s, true)
length *= 2
}
stream := &Stream{
readSeeker: s,
length: length,
sampleRate: r.SampleRate(),
}
// Read some data for performance (#297).
if _, ok := src.(io.Seeker); ok {
if _, err := stream.Read(make([]byte, 65536)); err != nil && err != io.EOF {
return nil, err
}
if _, err := stream.Seek(0, io.SeekStart); err != nil {
return nil, err
}
}
return stream, nil
}
type i16Stream struct { type i16Stream struct {
posInBytes int64 posInBytes int64
vorbisReader *oggvorbis.Reader vorbisReader *oggvorbis.Reader
@ -147,7 +189,7 @@ func decodeI16(in io.Reader) (*i16Stream, error) {
return s, nil return s, nil
} }
// DecodeWithoutResampling decodes Ogg/Vorbis data to playable stream. // DecodeWithoutResampling decodes Ogg/Vorbis data to playable stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// DecodeWithoutResampling returns error when decoding fails or IO error happens. // DecodeWithoutResampling returns error when decoding fails or IO error happens.
// //
@ -176,7 +218,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
return stream, nil return stream, nil
} }
// DecodeWithSampleRate decodes Ogg/Vorbis data to playable stream. // DecodeWithSampleRate decodes Ogg/Vorbis data to playable stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// DecodeWithSampleRate returns error when decoding fails or IO error happens. // DecodeWithSampleRate returns error when decoding fails or IO error happens.
// //
@ -214,7 +256,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
return stream, nil return stream, nil
} }
// Decode decodes Ogg/Vorbis data to playable stream. // Decode decodes Ogg/Vorbis data to playable stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// Decode returns error when decoding fails or IO error happens. // Decode returns error when decoding fails or IO error happens.
// //

View File

@ -62,6 +62,28 @@ func TestMono(t *testing.T) {
} }
} }
func TestMonoF32(t *testing.T) {
bs := test_mono_ogg
s, err := vorbis.DecodeF32(bytes.NewReader(bs))
if err != nil {
t.Fatal(err)
}
r, err := oggvorbis.NewReader(bytes.NewReader(bs))
if err != nil {
t.Fatal(err)
}
// Stream decoded by audio/vorbis.DecodeF32() is always 32bit float stereo.
// On the other hand, the original vorbis package is monoral.
// As Length() represents the number of samples,
// this needs to be doubled by 4 (= bytes in 32bits).
if got, want := s.Length(), r.Length()*2*4; got != want {
t.Errorf("s.Length(): got: %d, want: %d", got, want)
}
}
func TestTooShort(t *testing.T) { func TestTooShort(t *testing.T) {
bs := test_tooshort_ogg bs := test_tooshort_ogg
@ -79,6 +101,19 @@ func TestTooShort(t *testing.T) {
} }
} }
func TestTooShortF32(t *testing.T) {
bs := test_tooshort_ogg
s, err := vorbis.DecodeF32(bytes.NewReader(bs))
if err != nil {
t.Fatal(err)
}
if got, want := s.Length(), int64(158848); got != want {
t.Errorf("s.Length(): got: %d, want: %d", got, want)
}
}
type reader struct { type reader struct {
r io.Reader r io.Reader
} }
@ -103,3 +138,16 @@ func TestNonSeeker(t *testing.T) {
t.Errorf("s.SampleRate(): got: %d, want: %d", got, want) t.Errorf("s.SampleRate(): got: %d, want: %d", got, want)
} }
} }
func TestNonSeekerF32(t *testing.T) {
bs := test_tooshort_ogg
s, err := vorbis.DecodeF32(&reader{r: bytes.NewReader(bs)})
if err != nil {
t.Fatal(err)
}
if got, want := s.Length(), int64(0); got != want {
t.Errorf("s.Length(): got: %d, want: %d", got, want)
}
}

View File

@ -26,9 +26,14 @@ import (
const ( const (
bitDepthInBytesInt16 = 2 bitDepthInBytesInt16 = 2
bitDepthInBytesFloat32 = 4
) )
// Stream is a decoded audio stream. // Stream is a decoded audio stream.
//
// The format is signed 16bit integer little endian PCM (DecodeWithoutResampling, etc.),
// or 32bit float little endian PCM (DeocdeF32).
// The channel count is 2.
type Stream struct { type Stream struct {
inner io.ReadSeeker inner io.ReadSeeker
size int64 size int64
@ -59,10 +64,29 @@ func (s *Stream) SampleRate() int {
return s.sampleRate return s.sampleRate
} }
// DecodeWithoutResampling decodes WAV (RIFF) data to playable stream. // DecodeF32 decodes WAV (RIFF) data to playable stream in 32bit float, little endian, 2 channels (stereo) format.
// //
// The format must be 1 or 2 channels, 8bit or 16bit little endian PCM. // The src format must be 1 or 2 channels, 8bit or 16bit little endian PCM.
// The format is converted into 2 channels and 16bit. // The src format is converted into 2 channels and 16bit.
//
// DecodeF32 returns error when decoding fails or IO error happens.
//
// The returned Stream's Seek is available only when src is an io.Seeker.
//
// A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func DecodeF32(src io.Reader) (*Stream, error) {
s, err := decode(src, bitDepthInBytesFloat32)
if err != nil {
return nil, err
}
return s, nil
}
// DecodeWithoutResampling decodes WAV (RIFF) data to playable stream in signed 16bit integer, little endian, 2 channels (stereo) format.
//
// The src format must be 1 or 2 channels, 8bit or 16bit little endian PCM.
// The src format is converted into 2 channels and 16bit.
// //
// DecodeWithoutSampleRate returns error when decoding fails or IO error happens. // DecodeWithoutSampleRate returns error when decoding fails or IO error happens.
// //
@ -71,14 +95,14 @@ func (s *Stream) SampleRate() int {
// A Stream doesn't close src even if src implements io.Closer. // A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility. // Closing the source is src owner's responsibility.
func DecodeWithoutResampling(src io.Reader) (*Stream, error) { func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
s, err := decode(src) s, err := decode(src, bitDepthInBytesInt16)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s, nil return s, nil
} }
// DecodeWithSampleRate decodes WAV (RIFF) data to playable stream. // DecodeWithSampleRate decodes WAV (RIFF) data to playable stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// The format must be 1 or 2 channels, 8bit or 16bit little endian PCM. // The format must be 1 or 2 channels, 8bit or 16bit little endian PCM.
// The format is converted into 2 channels and 16bit. // The format is converted into 2 channels and 16bit.
@ -95,7 +119,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
// Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited. // Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited.
// Do not expect that Stream has a resampling cache even after whole data is played. // Do not expect that Stream has a resampling cache even after whole data is played.
func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
s, err := decode(src) s, err := decode(src, bitDepthInBytesInt16)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -112,7 +136,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
}, nil }, nil
} }
func decode(src io.Reader) (*Stream, error) { func decode(src io.Reader, bitDepthInBytes int) (*Stream, error) {
buf := make([]byte, 12) buf := make([]byte, 12)
n, err := io.ReadFull(src, buf) n, err := io.ReadFull(src, buf)
if n != len(buf) { if n != len(buf) {
@ -206,6 +230,12 @@ chunks:
dataSize *= 2 dataSize *= 2
} }
} }
if bitDepthInBytes == bitDepthInBytesFloat32 {
s = convert.NewFloat32BytesReadSeekerFromInt16BytesReadSeeker(s)
dataSize *= 2
}
return &Stream{ return &Stream{
inner: s, inner: s,
size: dataSize, size: dataSize,
@ -213,7 +243,7 @@ chunks:
}, nil }, nil
} }
// Decode decodes WAV (RIFF) data to playable stream. // Decode decodes WAV (RIFF) data to playable stream in signed 16bit integer, little endian, 2 channels (stereo) format.
// //
// The format must be 1 or 2 channels, 8bit or 16bit little endian PCM. // The format must be 1 or 2 channels, 8bit or 16bit little endian PCM.
// The format is converted into 2 channels and 16bit. // The format is converted into 2 channels and 16bit.

View File

@ -124,27 +124,28 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P
Length() int64 Length() int64
} }
const bytesPerSample = 4 // TODO: This should be defined in audio package // bytesPerSample is the byte size for one sample (8 [bytes] = 2 [channels] * 4 [bytes] (32bit float)).
// TODO: This should be defined in audio package.
const bytesPerSample = 8
var s audioStream var s audioStream
switch musicType { switch musicType {
case typeOgg: case typeOgg:
var err error var err error
s, err = vorbis.DecodeWithoutResampling(bytes.NewReader(raudio.Ragtime_ogg)) s, err = vorbis.DecodeF32(bytes.NewReader(raudio.Ragtime_ogg))
if err != nil { if err != nil {
return nil, err return nil, err
} }
case typeMP3: case typeMP3:
var err error var err error
s, err = mp3.DecodeWithoutResampling(bytes.NewReader(raudio.Ragtime_mp3)) s, err = mp3.DecodeF32(bytes.NewReader(raudio.Ragtime_mp3))
if err != nil { if err != nil {
return nil, err return nil, err
} }
default: default:
panic("not reached") panic("not reached")
} }
p, err := audioContext.NewPlayer(s) p, err := audioContext.NewPlayerF32(s)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -171,7 +172,7 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P
player.audioPlayer.Play() player.audioPlayer.Play()
go func() { go func() {
s, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav)) s, err := wav.DecodeF32(bytes.NewReader(raudio.Jab_wav))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
return return
@ -245,7 +246,7 @@ func (p *Player) playSEIfNeeded() {
if !p.shouldPlaySE() { if !p.shouldPlaySE() {
return return
} }
sePlayer := p.audioContext.NewPlayerFromBytes(p.seBytes) sePlayer := p.audioContext.NewPlayerF32FromBytes(p.seBytes)
sePlayer.Play() sePlayer.Play()
} }

View File

@ -31,7 +31,7 @@ const (
screenWidth = 640 screenWidth = 640
screenHeight = 480 screenHeight = 480
sampleRate = 48000 sampleRate = 48000
bytesPerSample = 4 // 2 channels * 2 bytes (16 bit) bytesPerSample = 8 // 2 channels * 4 bytes (32 bit float)
introLengthInSecond = 5 introLengthInSecond = 5
loopLengthInSecond = 4 loopLengthInSecond = 4
@ -53,16 +53,16 @@ func (g *Game) Update() error {
// Decode an Ogg file. // Decode an Ogg file.
// oggS is a decoded io.ReadCloser and io.Seeker. // oggS is a decoded io.ReadCloser and io.Seeker.
oggS, err := vorbis.DecodeWithoutResampling(bytes.NewReader(raudio.Ragtime_ogg)) oggS, err := vorbis.DecodeF32(bytes.NewReader(raudio.Ragtime_ogg))
if err != nil { if err != nil {
return err return err
} }
// Create an infinite loop stream from the decoded bytes. // Create an infinite loop stream from the decoded bytes.
// s is still an io.ReadCloser and io.Seeker. // s is still an io.ReadCloser and io.Seeker.
s := audio.NewInfiniteLoopWithIntro(oggS, introLengthInSecond*bytesPerSample*sampleRate, loopLengthInSecond*bytesPerSample*sampleRate) s := audio.NewInfiniteLoopWithIntroF32(oggS, introLengthInSecond*bytesPerSample*sampleRate, loopLengthInSecond*bytesPerSample*sampleRate)
g.player, err = g.audioContext.NewPlayer(s) g.player, err = g.audioContext.NewPlayerF32(s)
if err != nil { if err != nil {
return err return err
} }
@ -79,7 +79,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
} }
msg := fmt.Sprintf(`TPS: %0.2f msg := fmt.Sprintf(`TPS: %0.2f
This is an example using This is an example using
audio.NewInfiniteLoopWithIntro. audio.NewInfiniteLoopWithIntroF32.
Intro: 0[s] - %[2]d[s] Intro: 0[s] - %[2]d[s]
Loop: %[2]d[s] - %[3]d[s] Loop: %[2]d[s] - %[3]d[s]

View File

@ -67,7 +67,7 @@ func (g *Game) initAudioIfNeeded() {
// Decode an Ogg file. // Decode an Ogg file.
// oggS is a decoded io.ReadCloser and io.Seeker. // oggS is a decoded io.ReadCloser and io.Seeker.
oggS, err := vorbis.DecodeWithoutResampling(bytes.NewReader(raudio.Ragtime_ogg)) oggS, err := vorbis.DecodeF32(bytes.NewReader(raudio.Ragtime_ogg))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -76,7 +76,7 @@ func (g *Game) initAudioIfNeeded() {
g.panstream = NewStereoPanStream(audio.NewInfiniteLoop(oggS, oggS.Length())) g.panstream = NewStereoPanStream(audio.NewInfiniteLoop(oggS, oggS.Length()))
g.panstream.SetPan(g.panning) g.panstream.SetPan(g.panning)
g.player, err = g.audioContext.NewPlayer(g.panstream) g.player, err = g.audioContext.NewPlayerF32(g.panstream)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -162,7 +162,7 @@ func (s *StereoPanStream) Read(p []byte) (int, error) {
// Align the buffer size in multiples of 4. The extra part is pushed to the buffer for the // Align the buffer size in multiples of 4. The extra part is pushed to the buffer for the
// next time. // next time.
totalN := bufN + readN totalN := bufN + readN
extra := totalN - totalN/4*4 extra := totalN - totalN/8*8
s.buf = append(s.buf, p[totalN-extra:totalN]...) s.buf = append(s.buf, p[totalN-extra:totalN]...)
alignedN := totalN - extra alignedN := totalN - extra
@ -171,16 +171,22 @@ func (s *StereoPanStream) Read(p []byte) (int, error) {
// When pan is -1.0, only the left channel of the stereo sound is audible, when pan is 1.0, // When pan is -1.0, only the left channel of the stereo sound is audible, when pan is 1.0,
// only the right channel of the stereo sound is audible. // only the right channel of the stereo sound is audible.
// https://docs.unity3d.com/ScriptReference/AudioSource-panStereo.html // https://docs.unity3d.com/ScriptReference/AudioSource-panStereo.html
ls := math.Min(s.pan*-1+1, 1) ls := float32(math.Min(s.pan*-1+1, 1))
rs := math.Min(s.pan+1, 1) rs := float32(math.Min(s.pan+1, 1))
for i := 0; i < alignedN; i += 4 { for i := 0; i < alignedN; i += 8 {
lc := int16(float64(int16(p[i])|int16(p[i+1])<<8) * ls) lc := math.Float32frombits(uint32(p[i])|(uint32(p[i+1])<<8)|(uint32(p[i+2])<<16)|(uint32(p[i+3])<<24)) * ls
rc := int16(float64(int16(p[i+2])|int16(p[i+3])<<8) * rs) rc := math.Float32frombits(uint32(p[i+4])|(uint32(p[i+5])<<8)|(uint32(p[i+6])<<16)|(uint32(p[i+7])<<24)) * rs
lcBits := math.Float32bits(lc)
rcBits := math.Float32bits(rc)
p[i] = byte(lc) p[i] = byte(lcBits)
p[i+1] = byte(lc >> 8) p[i+1] = byte(lcBits >> 8)
p[i+2] = byte(rc) p[i+2] = byte(lcBits >> 16)
p[i+3] = byte(rc >> 8) p[i+3] = byte(lcBits >> 24)
p[i+4] = byte(rcBits)
p[i+5] = byte(rcBits >> 8)
p[i+6] = byte(rcBits >> 16)
p[i+7] = byte(rcBits >> 24)
} }
return alignedN, err return alignedN, err
} }

View File

@ -157,20 +157,20 @@ func (g *Game) init() {
g.audioContext = audio.NewContext(48000) g.audioContext = audio.NewContext(48000)
} }
jumpD, err := vorbis.DecodeWithoutResampling(bytes.NewReader(raudio.Jump_ogg)) jumpD, err := vorbis.DecodeF32(bytes.NewReader(raudio.Jump_ogg))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
g.jumpPlayer, err = g.audioContext.NewPlayer(jumpD) g.jumpPlayer, err = g.audioContext.NewPlayerF32(jumpD)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
jabD, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav)) jabD, err := wav.DecodeF32(bytes.NewReader(raudio.Jab_wav))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
g.hitPlayer, err = g.audioContext.NewPlayer(jabD) g.hitPlayer, err = g.audioContext.NewPlayerF32(jabD)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -61,7 +61,7 @@ func NewGame() (*Game, error) {
// return err // return err
// } // }
// //
// d, err := wav.DecodeWithoutResampling(f) // d, err := wav.DecodeF32(f)
// ... // ...
// Decode wav-formatted data and retrieve decoded PCM stream. // Decode wav-formatted data and retrieve decoded PCM stream.
@ -72,13 +72,13 @@ func NewGame() (*Game, error) {
default: default:
r = bytes.NewReader(raudio.Jab_wav) r = bytes.NewReader(raudio.Jab_wav)
} }
d, err := wav.DecodeWithoutResampling(r) d, err := wav.DecodeF32(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Create an audio.Player that has one stream. // Create an audio.Player that has one stream.
g.audioPlayer, err = g.audioContext.NewPlayer(d) g.audioPlayer, err = g.audioContext.NewPlayerF32(d)
if err != nil { if err != nil {
return nil, err return nil, err
} }