mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
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:
parent
1d7c350967
commit
f0ef1ecad0
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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
80
audio/vorbis/float32.go
Normal 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
|
||||||
|
}
|
@ -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.
|
||||||
//
|
//
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user