mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
audio: Blend the loop start and after-the-loop part to reduce noises at InfiniteLoop
Closes #1888
This commit is contained in:
parent
90f59aad7c
commit
c91a6fb81f
@ -152,3 +152,7 @@ func PlayersNumForTesting() int {
|
|||||||
func ResetContextForTesting() {
|
func ResetContextForTesting() {
|
||||||
theContext = nil
|
theContext = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *InfiniteLoop) SetNoBlendForTesting(value bool) {
|
||||||
|
i.noBlendForTesting = value
|
||||||
|
}
|
||||||
|
@ -25,6 +25,17 @@ type InfiniteLoop struct {
|
|||||||
lstart int64
|
lstart int64
|
||||||
llength int64
|
llength int64
|
||||||
pos int64
|
pos int64
|
||||||
|
|
||||||
|
// extra is the remainder in the case when the read byte sizes are not multiple of bitDepthInBytes.
|
||||||
|
extra []byte
|
||||||
|
|
||||||
|
// afterLoop is data after the loop.
|
||||||
|
afterLoop []byte
|
||||||
|
|
||||||
|
// blending represents whether the loop start and afterLoop are blended or not.
|
||||||
|
blending bool
|
||||||
|
|
||||||
|
noBlendForTesting bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@ -62,6 +73,18 @@ func (i *InfiniteLoop) ensurePos() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *InfiniteLoop) blendRate(pos int64) float64 {
|
||||||
|
if pos < i.lstart {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if pos >= i.lstart+int64(len(i.afterLoop)) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
p := (pos - i.lstart) / bytesPerSample
|
||||||
|
l := len(i.afterLoop) / bytesPerSample
|
||||||
|
return 1 - float64(p)/float64(l)
|
||||||
|
}
|
||||||
|
|
||||||
// Read is implementation of ReadSeeker's Read.
|
// Read is implementation of ReadSeeker's Read.
|
||||||
func (i *InfiniteLoop) Read(b []byte) (int, error) {
|
func (i *InfiniteLoop) Read(b []byte) (int, error) {
|
||||||
if err := i.ensurePos(); err != nil {
|
if err := i.ensurePos(); err != nil {
|
||||||
@ -72,17 +95,80 @@ func (i *InfiniteLoop) Read(b []byte) (int, error) {
|
|||||||
b = b[:i.length()-i.pos]
|
b = b[:i.length()-i.pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := i.src.Read(b)
|
extralen := len(i.extra)
|
||||||
|
copy(b, i.extra)
|
||||||
|
i.extra = i.extra[:0]
|
||||||
|
|
||||||
|
n, err := i.src.Read(b[extralen:])
|
||||||
|
n += extralen
|
||||||
i.pos += int64(n)
|
i.pos += int64(n)
|
||||||
if i.pos > i.length() {
|
if i.pos > i.length() {
|
||||||
panic(fmt.Sprintf("audio: position must be <= length but not at (*InfiniteLoop).Read: pos: %d, length: %d", i.pos, i.length()))
|
panic(fmt.Sprintf("audio: position must be <= length but not at (*InfiniteLoop).Read: pos: %d, length: %d", i.pos, i.length()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the remainder part to extra. This will be used at the next Read.
|
||||||
|
if rem := n % bitDepthInBytes; rem != 0 {
|
||||||
|
i.extra = append(i.extra, b[n-rem:n]...)
|
||||||
|
b = b[:n-rem]
|
||||||
|
n = n - rem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blend afterLoop and the loop start to reduce noises (#1888).
|
||||||
|
// Ideally, afterLoop and the loop start should be identical, but they can have very slight differences.
|
||||||
|
if !i.noBlendForTesting && i.blending && i.pos >= i.lstart && i.pos-int64(n) < i.lstart+int64(len(i.afterLoop)) {
|
||||||
|
if n%bitDepthInBytes != 0 {
|
||||||
|
panic(fmt.Sprintf("audio: n must be a multiple of bitDepthInBytes but not: %d", n))
|
||||||
|
}
|
||||||
|
for idx := 0; idx < n/bitDepthInBytes; idx++ {
|
||||||
|
abspos := i.pos - int64(n) + int64(idx)*bitDepthInBytes
|
||||||
|
rate := i.blendRate(abspos)
|
||||||
|
if rate == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// This assumes that bitDepthInBytes is 2.
|
||||||
|
relpos := abspos - i.lstart
|
||||||
|
afterLoop := int16(i.afterLoop[relpos]) | (int16(i.afterLoop[relpos+1]) << 8)
|
||||||
|
orig := int16(b[2*idx]) | (int16(b[2*idx+1]) << 8)
|
||||||
|
|
||||||
|
newval := int16(float64(afterLoop)*rate + float64(orig)*(1-rate))
|
||||||
|
b[2*idx] = byte(newval)
|
||||||
|
b[2*idx+1] = byte(newval >> 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF || i.pos == i.length() {
|
// Read the afterLoop part if necessary.
|
||||||
|
if i.pos == i.length() && err == nil {
|
||||||
|
if i.afterLoop == nil {
|
||||||
|
buflen := int64(256 * bytesPerSample)
|
||||||
|
if buflen > i.length() {
|
||||||
|
buflen = i.length()
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, buflen)
|
||||||
|
pos := 0
|
||||||
|
for pos < len(buf) {
|
||||||
|
n, err := i.src.Read(buf[pos:])
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
pos += n
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.afterLoop = buf[:pos]
|
||||||
|
}
|
||||||
|
if len(i.afterLoop) > 0 {
|
||||||
|
i.blending = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.pos == i.length() || err == io.EOF {
|
||||||
// Ignore the new position returned by Seek since the source position might not be match with the position
|
// Ignore the new position returned by Seek since the source position might not be match with the position
|
||||||
// managed by this.
|
// managed by this.
|
||||||
if _, err := i.src.Seek(i.lstart, io.SeekStart); err != nil {
|
if _, err := i.src.Seek(i.lstart, io.SeekStart); err != nil {
|
||||||
@ -95,6 +181,7 @@ func (i *InfiniteLoop) Read(b []byte) (int, error) {
|
|||||||
|
|
||||||
// Seek is implementation of ReadSeeker's Seek.
|
// Seek is implementation of ReadSeeker's Seek.
|
||||||
func (i *InfiniteLoop) Seek(offset int64, whence int) (int64, error) {
|
func (i *InfiniteLoop) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
i.blending = false
|
||||||
if err := i.ensurePos(); err != nil {
|
if err := i.ensurePos(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,9 @@ func TestInfiniteLoopWithIntro(t *testing.T) {
|
|||||||
src[i] = indexToByte(i)
|
src[i] = indexToByte(i)
|
||||||
}
|
}
|
||||||
srcInf := audio.NewInfiniteLoop(bytes.NewReader(src), srcLength)
|
srcInf := audio.NewInfiniteLoop(bytes.NewReader(src), srcLength)
|
||||||
|
srcInf.SetNoBlendForTesting(true)
|
||||||
l := audio.NewInfiniteLoopWithIntro(srcInf, introLength, loopLength)
|
l := audio.NewInfiniteLoopWithIntro(srcInf, introLength, loopLength)
|
||||||
|
l.SetNoBlendForTesting(true)
|
||||||
|
|
||||||
buf := make([]byte, srcLength*4)
|
buf := make([]byte, srcLength*4)
|
||||||
if _, err := io.ReadFull(l, buf); err != nil {
|
if _, err := io.ReadFull(l, buf); err != nil {
|
||||||
@ -170,3 +172,88 @@ func TestInfiniteLoopWithIncompleteSize(t *testing.T) {
|
|||||||
t.Errorf("got: %d, want: %d", got, want)
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type slowReader struct {
|
||||||
|
src io.ReadSeeker
|
||||||
|
eof bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slowReader) Read(buf []byte) (int, error) {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
if s.eof {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := s.src.Read(buf[:1])
|
||||||
|
if err == io.EOF {
|
||||||
|
s.eof = true
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slowReader) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
s.eof = false
|
||||||
|
return s.src.Seek(offset, whence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfiniteLoopWithSlowSource(t *testing.T) {
|
||||||
|
src := make([]byte, 4096)
|
||||||
|
for i := range src {
|
||||||
|
src[i] = byte(i)
|
||||||
|
}
|
||||||
|
r := &slowReader{
|
||||||
|
src: bytes.NewReader(src),
|
||||||
|
}
|
||||||
|
loop := audio.NewInfiniteLoop(r, 4096)
|
||||||
|
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
|
||||||
|
// With a slow source, whose Read always reads at most one byte,
|
||||||
|
// an infinite loop should adjust the reading size along with bitDepthInBytes (= 2).
|
||||||
|
|
||||||
|
n0, err := loop.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got, want := n0, 0; got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
n1, err := loop.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got, want := n1, 2; got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := buf[0], byte(0); got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := buf[1], byte(1); got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
n2, err := loop.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got, want := n2, 0; got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
n3, err := loop.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got, want := n3, 2; got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := buf[0], byte(2); got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := buf[1], byte(3); got != want {
|
||||||
|
t.Errorf("got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user