Skip to content

Commit

Permalink
Merge pull request #127 from gopxl/fix-flac-decode-eof
Browse files Browse the repository at this point in the history
Fix flac.Decode handling of io.EOF
  • Loading branch information
duysqubix authored Nov 2, 2023
2 parents b7ad46a + 0d4fd3c commit f20a09c
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 74 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for single channel ogg/vorbis ([#10](https://github.com/gopxl/beep/pull/10))

### Fixed
- Fix FileSize for saving .wav ([#6](https://github.com/gopxl/beep/pull/6))
- Fix `FileSize` for saving .wav ([#6](https://github.com/gopxl/beep/pull/6))
- Fix `flac.Decode` handling of `io.EOF` ([#127](https://github.com/gopxl/beep/pull/127))

### Changed
- Upgrade Go version to 1.21 ([#2](https://github.com/gopxl/beep/pull/2))
Expand Down
76 changes: 10 additions & 66 deletions compositors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,17 @@ import (
"testing"

"github.com/gopxl/beep"
"github.com/gopxl/beep/internal/testtools"
)

// randomDataStreamer generates random samples of duration d and returns a StreamSeeker which streams
// them and the data itself.
func randomDataStreamer(numSamples int) (s beep.StreamSeeker, data [][2]float64) {
data = make([][2]float64, numSamples)
for i := range data {
data[i][0] = rand.Float64()*2 - 1
data[i][1] = rand.Float64()*2 - 1
}
return &dataStreamer{data, 0}, data
}

type dataStreamer struct {
data [][2]float64
pos int
}

func (ds *dataStreamer) Stream(samples [][2]float64) (n int, ok bool) {
if ds.pos >= len(ds.data) {
return 0, false
}
n = copy(samples, ds.data[ds.pos:])
ds.pos += n
return n, true
}

func (ds *dataStreamer) Err() error {
return nil
}

func (ds *dataStreamer) Len() int {
return len(ds.data)
}

func (ds *dataStreamer) Position() int {
return ds.pos
}

func (ds *dataStreamer) Seek(p int) error {
ds.pos = p
return nil
}

// collect drains Streamer s and returns all of the samples it streamed.
func collect(s beep.Streamer) [][2]float64 {
var (
result [][2]float64
buf [479][2]float64
)
for {
n, ok := s.Stream(buf[:])
if !ok {
return result
}
result = append(result, buf[:n]...)
}
}

func TestTake(t *testing.T) {
for i := 0; i < 7; i++ {
total := rand.Intn(1e5) + 1e4
s, data := randomDataStreamer(total)
s, data := testtools.RandomDataStreamer(total)
take := rand.Intn(total)

want := data[:take]
got := collect(beep.Take(take, s))
got := testtools.Collect(beep.Take(take, s))

if !reflect.DeepEqual(want, got) {
t.Error("Take not working correctly")
Expand All @@ -83,13 +27,13 @@ func TestTake(t *testing.T) {
func TestLoop(t *testing.T) {
for i := 0; i < 7; i++ {
for n := 0; n < 5; n++ {
s, data := randomDataStreamer(10)
s, data := testtools.RandomDataStreamer(10)

var want [][2]float64
for j := 0; j < n; j++ {
want = append(want, data...)
}
got := collect(beep.Loop(n, s))
got := testtools.Collect(beep.Loop(n, s))

if !reflect.DeepEqual(want, got) {
t.Error("Loop not working correctly")
Expand All @@ -105,15 +49,15 @@ func TestSeq(t *testing.T) {
data = make([][][2]float64, n)
)
for i := range s {
s[i], data[i] = randomDataStreamer(rand.Intn(1e5) + 1e4)
s[i], data[i] = testtools.RandomDataStreamer(rand.Intn(1e5) + 1e4)
}

var want [][2]float64
for _, d := range data {
want = append(want, d...)
}

got := collect(beep.Seq(s...))
got := testtools.Collect(beep.Seq(s...))

if !reflect.DeepEqual(want, got) {
t.Errorf("Seq not working properly")
Expand All @@ -127,7 +71,7 @@ func TestMix(t *testing.T) {
data = make([][][2]float64, n)
)
for i := range s {
s[i], data[i] = randomDataStreamer(rand.Intn(1e5) + 1e4)
s[i], data[i] = testtools.RandomDataStreamer(rand.Intn(1e5) + 1e4)
}

maxLen := 0
Expand All @@ -145,7 +89,7 @@ func TestMix(t *testing.T) {
}
}

got := collect(beep.Mix(s...))
got := testtools.Collect(beep.Mix(s...))

if !reflect.DeepEqual(want, got) {
t.Error("Mix not working correctly")
Expand All @@ -154,7 +98,7 @@ func TestMix(t *testing.T) {

func TestDup(t *testing.T) {
for i := 0; i < 7; i++ {
s, data := randomDataStreamer(rand.Intn(1e5) + 1e4)
s, data := testtools.RandomDataStreamer(rand.Intn(1e5) + 1e4)
st, su := beep.Dup(s)

var tData, uData [][2]float64
Expand Down
10 changes: 7 additions & 3 deletions flac/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"fmt"
"io"

"github.com/gopxl/beep"
"github.com/mewkiz/flac"
"github.com/pkg/errors"

"github.com/gopxl/beep"
)

// Decode takes a Reader containing audio data in FLAC format and returns a StreamSeekCloser,
Expand Down Expand Up @@ -62,9 +63,12 @@ func (d *decoder) Stream(samples [][2]float64) (n int, ok bool) {
if j >= len(d.buf) {
// refill buffer.
if err := d.refill(); err != nil {
d.err = err
d.pos += n
return n, n > 0
if err == io.EOF {
return n, n > 0
}
d.err = err
return 0, false
}
j = 0
}
Expand Down
39 changes: 39 additions & 0 deletions flac/decode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package flac_test

import (
"os"
"testing"

"github.com/stretchr/testify/assert"

"github.com/gopxl/beep/flac"
"github.com/gopxl/beep/internal/testtools"
)

func TestDecoder_Stream(t *testing.T) {
f, err := os.Open(testtools.TestFilePath("valid_44100hz_22050_samples.flac"))
assert.NoError(t, err)
defer f.Close()

streamer, _, err := flac.Decode(f)
assert.NoError(t, err)

// Case 1: return ok with all requested samples
buf := testtools.CollectNum(22000, streamer)
assert.Lenf(t, buf, 22000, "streamer quit prematurely; expected %d samples, got %d", 22000, len(buf))
assert.NoError(t, streamer.Err())

buf = make([][2]float64, 512)

// Case 2: return ok with 0 < n < 512 samples
n, ok := streamer.Stream(buf[:])
assert.True(t, ok)
assert.Equal(t, 50, n)
assert.NoError(t, streamer.Err())

// Case 3: return !ok with n == 0
n, ok = streamer.Stream(buf[:])
assert.False(t, ok)
assert.Equal(t, 0, n)
assert.NoError(t, streamer.Err())
}
4 changes: 2 additions & 2 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ type Streamer interface {
// samples will be produced, it returns 0 and false. Stream must not touch any samples
// outside samples[:n].
//
// There are 3 valid return pattterns of the Stream method:
// There are 3 valid return patterns of the Stream method:
//
// 1. n == len(samples) && ok
//
// Stream streamed all of the requested samples. Cases 1, 2 and 3 may occur in the following
// Stream streamed all the requested samples. Cases 1, 2 and 3 may occur in the following
// calls.
//
// 2. 0 < n && n < len(samples) && ok
Expand Down
Binary file not shown.
23 changes: 23 additions & 0 deletions internal/testtools/sinks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package testtools

import "github.com/gopxl/beep"

// Collect drains Streamer s and returns all the samples it streamed.
func Collect(s beep.Streamer) [][2]float64 {
var (
result [][2]float64
buf [479][2]float64
)
for {
n, ok := s.Stream(buf[:])
if !ok {
return result
}
result = append(result, buf[:n]...)
}
}

// CollectNum collects num samples from the streamer in chunks and returns them all.
func CollectNum(num int, s beep.Streamer) [][2]float64 {
return Collect(beep.Take(num, s))
}
48 changes: 48 additions & 0 deletions internal/testtools/streamers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package testtools

import (
"math/rand"

"github.com/gopxl/beep"
)

// RandomDataStreamer generates numSamples random samples and returns a StreamSeeker to stream them.
func RandomDataStreamer(numSamples int) (s beep.StreamSeeker, data [][2]float64) {
data = make([][2]float64, numSamples)
for i := range data {
data[i][0] = rand.Float64()*2 - 1
data[i][1] = rand.Float64()*2 - 1
}
return &dataStreamer{data, 0}, data
}

type dataStreamer struct {
data [][2]float64
pos int
}

func (ds *dataStreamer) Stream(samples [][2]float64) (n int, ok bool) {
if ds.pos >= len(ds.data) {
return 0, false
}
n = copy(samples, ds.data[ds.pos:])
ds.pos += n
return n, true
}

func (ds *dataStreamer) Err() error {
return nil
}

func (ds *dataStreamer) Len() int {
return len(ds.data)
}

func (ds *dataStreamer) Position() int {
return ds.pos
}

func (ds *dataStreamer) Seek(p int) error {
ds.pos = p
return nil
}
16 changes: 16 additions & 0 deletions internal/testtools/testdata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package testtools

import (
"path/filepath"
"runtime"
)

var (
// See https://stackoverflow.com/a/38644571
_, callerPath, _, _ = runtime.Caller(0)
TestDataDir = filepath.Join(filepath.Dir(callerPath), "../testdata")
)

func TestFilePath(path string) string {
return filepath.Join(TestDataDir, path)
}
5 changes: 3 additions & 2 deletions resample_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/gopxl/beep"
"github.com/gopxl/beep/internal/testtools"
)

func TestResample(t *testing.T) {
Expand All @@ -15,11 +16,11 @@ func TestResample(t *testing.T) {
continue // skip too expensive combinations
}

s, data := randomDataStreamer(numSamples)
s, data := testtools.RandomDataStreamer(numSamples)

want := resampleCorrect(3, old, new, data)

got := collect(beep.Resample(3, old, new, s))
got := testtools.Collect(beep.Resample(3, old, new, s))

if !reflect.DeepEqual(want, got) {
t.Fatal("Resample not working correctly")
Expand Down

0 comments on commit f20a09c

Please sign in to comment.