diff --git a/audio/audio.go b/audio/audio.go index bce5d1ab6..bbfecbcf1 100644 --- a/audio/audio.go +++ b/audio/audio.go @@ -350,6 +350,36 @@ func (c *Context) NewPlayer(src io.Reader) (*Player, error) { 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. // // Deprecated: as of v2.2. Use (*Context).NewPlayer instead. @@ -372,6 +402,21 @@ func (c *Context) NewPlayerFromBytes(src []byte) *Player { 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. // // Deprecated: as of v2.2. Use (*Context).NewPlayerFromBytes instead. @@ -499,7 +544,7 @@ func (h *hookerImpl) AppendHookOnBeforeUpdate(f func() error) { 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. // from is the original 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) } + +// 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) +} diff --git a/audio/internal/convert/float32.go b/audio/internal/convert/float32.go index 114d637a2..3908b2dac 100644 --- a/audio/internal/convert/float32.go +++ b/audio/internal/convert/float32.go @@ -24,6 +24,10 @@ func NewFloat32BytesReaderFromInt16BytesReader(r io.Reader) io.Reader { return &float32BytesReader{r: r} } +func NewFloat32BytesReadSeekerFromInt16BytesReadSeeker(r io.ReadSeeker) io.ReadSeeker { + return &float32BytesReader{r: r} +} + type float32BytesReader struct { r io.Reader eof bool diff --git a/audio/loop.go b/audio/loop.go index fcc406a08..ee9d911bf 100644 --- a/audio/loop.go +++ b/audio/loop.go @@ -17,6 +17,7 @@ package audio import ( "fmt" "io" + "math" ) // 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. // +// 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. // 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 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 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. // 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. @@ -63,6 +81,20 @@ func NewInfiniteLoopWithIntro(src io.ReadSeeker, introLength int64, loopLength i 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 { bytesPerSample := bitDepthInBytes * channelCount return &InfiniteLoop{ @@ -152,9 +184,18 @@ func (i *InfiniteLoop) Read(b []byte) (int, error) { case 2: afterLoop := int16(i.afterLoop[relpos]) | (int16(i.afterLoop[relpos+1]) << 8) orig := int16(b[2*idx]) | (int16(b[2*idx+1]) << 8) - newval := int16(float32(afterLoop)*rate + float32(orig)*(1-rate)) - b[2*idx] = byte(newval) - b[2*idx+1] = byte(newval >> 8) + newVal := int16(float32(afterLoop)*rate + float32(orig)*(1-rate)) + b[2*idx] = byte(newVal) + 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: panic("not reached") } diff --git a/audio/mp3/decode.go b/audio/mp3/decode.go index 99f027eae..34e5226aa 100644 --- a/audio/mp3/decode.go +++ b/audio/mp3/decode.go @@ -28,7 +28,8 @@ import ( ) const ( - bitDepthInBytesInt16 = 2 + bitDepthInBytesInt16 = 2 + bitDepthInBytesFloat32 = 4 ) // Stream is a decoded stream. @@ -58,7 +59,29 @@ func (s *Stream) SampleRate() int { 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. // @@ -79,7 +102,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) { 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. // @@ -113,7 +136,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { 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. // diff --git a/audio/vorbis/float32.go b/audio/vorbis/float32.go new file mode 100644 index 000000000..05a98425f --- /dev/null +++ b/audio/vorbis/float32.go @@ -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 +} diff --git a/audio/vorbis/vorbis.go b/audio/vorbis/vorbis.go index 73eee54f9..960091a26 100644 --- a/audio/vorbis/vorbis.go +++ b/audio/vorbis/vorbis.go @@ -26,7 +26,8 @@ import ( ) const ( - bitDepthInBytesInt16 = 2 + bitDepthInBytesInt16 = 2 + bitDepthInBytesFloat32 = 4 ) // Stream is a decoded audio stream. @@ -60,6 +61,47 @@ func (s *Stream) SampleRate() int { 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 { posInBytes int64 vorbisReader *oggvorbis.Reader @@ -147,7 +189,7 @@ func decodeI16(in io.Reader) (*i16Stream, error) { 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. // @@ -176,7 +218,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) { 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. // @@ -214,7 +256,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { 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. // diff --git a/audio/vorbis/vorbis_test.go b/audio/vorbis/vorbis_test.go index ef028e333..e9f5e7327 100644 --- a/audio/vorbis/vorbis_test.go +++ b/audio/vorbis/vorbis_test.go @@ -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) { 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 { r io.Reader } @@ -103,3 +138,16 @@ func TestNonSeeker(t *testing.T) { 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) + } +} diff --git a/audio/wav/decode.go b/audio/wav/decode.go index ad33da913..1dd3fb2db 100644 --- a/audio/wav/decode.go +++ b/audio/wav/decode.go @@ -25,10 +25,15 @@ import ( ) const ( - bitDepthInBytesInt16 = 2 + bitDepthInBytesInt16 = 2 + bitDepthInBytesFloat32 = 4 ) // 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 { inner io.ReadSeeker size int64 @@ -59,10 +64,29 @@ func (s *Stream) SampleRate() int { 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 format is converted into 2 channels and 16bit. +// 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. +// +// 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. // @@ -71,14 +95,14 @@ func (s *Stream) SampleRate() int { // A Stream doesn't close src even if src implements io.Closer. // Closing the source is src owner's responsibility. func DecodeWithoutResampling(src io.Reader) (*Stream, error) { - s, err := decode(src) + s, err := decode(src, bitDepthInBytesInt16) if err != nil { return nil, err } 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 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. // Do not expect that Stream has a resampling cache even after whole data is played. func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { - s, err := decode(src) + s, err := decode(src, bitDepthInBytesInt16) if err != nil { return nil, err } @@ -112,7 +136,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { }, nil } -func decode(src io.Reader) (*Stream, error) { +func decode(src io.Reader, bitDepthInBytes int) (*Stream, error) { buf := make([]byte, 12) n, err := io.ReadFull(src, buf) if n != len(buf) { @@ -206,6 +230,12 @@ chunks: dataSize *= 2 } } + + if bitDepthInBytes == bitDepthInBytesFloat32 { + s = convert.NewFloat32BytesReadSeekerFromInt16BytesReadSeeker(s) + dataSize *= 2 + } + return &Stream{ inner: s, size: dataSize, @@ -213,7 +243,7 @@ chunks: }, 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 is converted into 2 channels and 16bit. diff --git a/examples/audio/main.go b/examples/audio/main.go index 5d8ca344b..504e195c9 100644 --- a/examples/audio/main.go +++ b/examples/audio/main.go @@ -124,27 +124,28 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P 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 switch musicType { case typeOgg: var err error - s, err = vorbis.DecodeWithoutResampling(bytes.NewReader(raudio.Ragtime_ogg)) + s, err = vorbis.DecodeF32(bytes.NewReader(raudio.Ragtime_ogg)) if err != nil { return nil, err } case typeMP3: var err error - s, err = mp3.DecodeWithoutResampling(bytes.NewReader(raudio.Ragtime_mp3)) + s, err = mp3.DecodeF32(bytes.NewReader(raudio.Ragtime_mp3)) if err != nil { return nil, err } default: panic("not reached") } - p, err := audioContext.NewPlayer(s) + p, err := audioContext.NewPlayerF32(s) if err != nil { return nil, err } @@ -171,7 +172,7 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P player.audioPlayer.Play() go func() { - s, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav)) + s, err := wav.DecodeF32(bytes.NewReader(raudio.Jab_wav)) if err != nil { log.Fatal(err) return @@ -245,7 +246,7 @@ func (p *Player) playSEIfNeeded() { if !p.shouldPlaySE() { return } - sePlayer := p.audioContext.NewPlayerFromBytes(p.seBytes) + sePlayer := p.audioContext.NewPlayerF32FromBytes(p.seBytes) sePlayer.Play() } diff --git a/examples/audioinfiniteloop/main.go b/examples/audioinfiniteloop/main.go index e444c80eb..6ae7ea080 100644 --- a/examples/audioinfiniteloop/main.go +++ b/examples/audioinfiniteloop/main.go @@ -31,7 +31,7 @@ const ( screenWidth = 640 screenHeight = 480 sampleRate = 48000 - bytesPerSample = 4 // 2 channels * 2 bytes (16 bit) + bytesPerSample = 8 // 2 channels * 4 bytes (32 bit float) introLengthInSecond = 5 loopLengthInSecond = 4 @@ -53,16 +53,16 @@ func (g *Game) Update() error { // Decode an Ogg file. // 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 { return err } // Create an infinite loop stream from the decoded bytes. // 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 { return err } @@ -79,7 +79,7 @@ func (g *Game) Draw(screen *ebiten.Image) { } msg := fmt.Sprintf(`TPS: %0.2f This is an example using -audio.NewInfiniteLoopWithIntro. +audio.NewInfiniteLoopWithIntroF32. Intro: 0[s] - %[2]d[s] Loop: %[2]d[s] - %[3]d[s] diff --git a/examples/audiopanning/main.go b/examples/audiopanning/main.go index aeca52b61..c496ba641 100644 --- a/examples/audiopanning/main.go +++ b/examples/audiopanning/main.go @@ -67,7 +67,7 @@ func (g *Game) initAudioIfNeeded() { // Decode an Ogg file. // 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 { log.Fatal(err) } @@ -76,7 +76,7 @@ func (g *Game) initAudioIfNeeded() { g.panstream = NewStereoPanStream(audio.NewInfiniteLoop(oggS, oggS.Length())) g.panstream.SetPan(g.panning) - g.player, err = g.audioContext.NewPlayer(g.panstream) + g.player, err = g.audioContext.NewPlayerF32(g.panstream) if err != nil { 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 // next time. totalN := bufN + readN - extra := totalN - totalN/4*4 + extra := totalN - totalN/8*8 s.buf = append(s.buf, p[totalN-extra:totalN]...) 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, // only the right channel of the stereo sound is audible. // https://docs.unity3d.com/ScriptReference/AudioSource-panStereo.html - ls := math.Min(s.pan*-1+1, 1) - rs := math.Min(s.pan+1, 1) - for i := 0; i < alignedN; i += 4 { - lc := int16(float64(int16(p[i])|int16(p[i+1])<<8) * ls) - rc := int16(float64(int16(p[i+2])|int16(p[i+3])<<8) * rs) + ls := float32(math.Min(s.pan*-1+1, 1)) + rs := float32(math.Min(s.pan+1, 1)) + for i := 0; i < alignedN; i += 8 { + lc := math.Float32frombits(uint32(p[i])|(uint32(p[i+1])<<8)|(uint32(p[i+2])<<16)|(uint32(p[i+3])<<24)) * ls + 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+1] = byte(lc >> 8) - p[i+2] = byte(rc) - p[i+3] = byte(rc >> 8) + p[i] = byte(lcBits) + p[i+1] = byte(lcBits >> 8) + p[i+2] = byte(lcBits >> 16) + 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 } diff --git a/examples/flappy/main.go b/examples/flappy/main.go index 32df62ec8..186ea2002 100644 --- a/examples/flappy/main.go +++ b/examples/flappy/main.go @@ -157,20 +157,20 @@ func (g *Game) init() { 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 { log.Fatal(err) } - g.jumpPlayer, err = g.audioContext.NewPlayer(jumpD) + g.jumpPlayer, err = g.audioContext.NewPlayerF32(jumpD) if err != nil { log.Fatal(err) } - jabD, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav)) + jabD, err := wav.DecodeF32(bytes.NewReader(raudio.Jab_wav)) if err != nil { log.Fatal(err) } - g.hitPlayer, err = g.audioContext.NewPlayer(jabD) + g.hitPlayer, err = g.audioContext.NewPlayerF32(jabD) if err != nil { log.Fatal(err) } diff --git a/examples/wav/main.go b/examples/wav/main.go index 27e123afa..5fbb25627 100644 --- a/examples/wav/main.go +++ b/examples/wav/main.go @@ -61,7 +61,7 @@ func NewGame() (*Game, error) { // return err // } // - // d, err := wav.DecodeWithoutResampling(f) + // d, err := wav.DecodeF32(f) // ... // Decode wav-formatted data and retrieve decoded PCM stream. @@ -72,13 +72,13 @@ func NewGame() (*Game, error) { default: r = bytes.NewReader(raudio.Jab_wav) } - d, err := wav.DecodeWithoutResampling(r) + d, err := wav.DecodeF32(r) if err != nil { return nil, err } // 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 { return nil, err }