Skip to content

Commit

Permalink
Deprecate original Loop but keep it alongside the new Loop2
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkKremer committed Sep 5, 2024
1 parent 31f26b5 commit dc89327
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 37 deletions.
92 changes: 77 additions & 15 deletions compositors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,69 @@ func (t *take) Err() error {
return t.s.Err()
}

type LoopOption func(opts *loop)
// Loop takes a StreamSeeker and plays it count times. If count is negative, s is looped infinitely.
//
// The returned Streamer propagates s's errors.
//
// Deprecated: use Loop2 instead. A call to Loop can be rewritten as follows:
// - beep.Loop(-1, s) -> beep.Loop2(s)
// - beep.Loop(0, s) -> no longer supported, use beep.Ctrl instead.
// - beep.Loop(3, s) -> beep.Loop2(s, beep.LoopTimes(2))
// Note that beep.LoopTimes takes the number of repeats instead of the number of total plays.
func Loop(count int, s StreamSeeker) Streamer {
return &loop{
s: s,
remains: count,
}
}

type loop struct {
s StreamSeeker
remains int
}

func (l *loop) Stream(samples [][2]float64) (n int, ok bool) {
if l.remains == 0 || l.s.Err() != nil {
return 0, false
}
for len(samples) > 0 {
sn, sok := l.s.Stream(samples)
if !sok {
if l.remains > 0 {
l.remains--
}
if l.remains == 0 {
break
}
err := l.s.Seek(0)
if err != nil {
return n, true
}
continue
}
samples = samples[sn:]
n += sn
}
return n, true
}

func (l *loop) Err() error {
return l.s.Err()
}

type LoopOption func(opts *loop2)

// LoopTimes sets how many times the source stream will repeat. If a section is defined
// by LoopStart, LoopEnd, or LoopBetween, only that section will repeat.
// A value of 0 plays the stream or section once (no repetition); 1 plays it twice, and so on.
func LoopTimes(times int) LoopOption {
if times < 0 {
panic("invalid argument to LoopTimes; times cannot be negative")
}
return func(loop *loop2) {
loop.remains = times
}
}

// LoopStart sets the position in the source stream to which it returns (using Seek())
// after reaching the end of the stream or the position set using LoopEnd. The samples
Expand All @@ -43,7 +105,7 @@ func LoopStart(pos int) LoopOption {
if pos < 0 {
panic("invalid argument to LoopStart; pos cannot be negative")
}
return func(loop *loop) {
return func(loop *loop2) {
loop.start = pos
}
}
Expand All @@ -55,31 +117,31 @@ func LoopEnd(pos int) LoopOption {
if pos < 0 {
panic("invalid argument to LoopEnd; pos cannot be negative")
}
return func(loop *loop) {
return func(loop *loop2) {
loop.end = pos
}
}

// LoopBetween sets both the LoopStart and LoopEnd positions simultaneously, specifying
// the section of the stream that will be looped.
func LoopBetween(start, end int) LoopOption {
return func(opts *loop) {
return func(opts *loop2) {
LoopStart(start)(opts)
LoopEnd(end)(opts)
}
}

// Loop takes a StreamSeeker and plays it the specified number of times. If count is negative,
// s loops indefinitely. LoopStart, LoopEnd, or LoopBetween can be used to define a specific
// section of the stream to loop. The samples before the start and after the end positions are
// played once before and after the looping section, respectively.
// Loop2 takes a StreamSeeker and repeats it according to the specified options. If no LoopTimes
// option is provided, the stream loops indefinitely. LoopStart, LoopEnd, or LoopBetween can define
// a specific section of the stream to loop. Samples before the start and after the end positions
// are played once before and after the looping section, respectively.
//
// The returned Streamer propagates any errors from s.
func Loop(count int, s StreamSeeker, opts ...LoopOption) Streamer {
l := &loop{
func Loop2(s StreamSeeker, opts ...LoopOption) Streamer {
l := &loop2{
s: s,
remains: count,
finished: count == 0,
remains: -1, // indefinitely
finished: false,
start: 0,
end: math.MaxInt,
}
Expand All @@ -99,7 +161,7 @@ func Loop(count int, s StreamSeeker, opts ...LoopOption) Streamer {
return l
}

type loop struct {
type loop2 struct {
s StreamSeeker
remains int // number of seeks remaining.
finished bool
Expand All @@ -108,7 +170,7 @@ type loop struct {
err error
}

func (l *loop) Stream(samples [][2]float64) (n int, ok bool) {
func (l *loop2) Stream(samples [][2]float64) (n int, ok bool) {
if l.finished || l.err != nil {
return 0, false
}
Expand Down Expand Up @@ -143,7 +205,7 @@ func (l *loop) Stream(samples [][2]float64) (n int, ok bool) {
return n, true
}

func (l *loop) Err() error {
func (l *loop2) Err() error {
return l.err
}

Expand Down
61 changes: 39 additions & 22 deletions compositors_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package beep_test

import (
"errors"
"math/rand"
"reflect"
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"

"github.com/gopxl/beep/v2"
Expand All @@ -28,67 +28,84 @@ func TestTake(t *testing.T) {
}

func TestLoop(t *testing.T) {
// Test no loop.
// For backwards compatibility, a loop count of 0 means that nothing at all will be played.
for i := 0; i < 7; i++ {
for n := 0; n < 5; n++ {
s, data := testtools.RandomDataStreamer(10)

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

if !reflect.DeepEqual(want, got) {
t.Error("Loop not working correctly")
}
}
}
}

func TestLoop2(t *testing.T) {
// Loop indefinitely (no options).
s, _ := testtools.NewSequentialDataStreamer(5)
got := testtools.Collect(beep.Loop(0, s))
assert.Empty(t, got)
got := testtools.CollectNum(16, beep.Loop2(s))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}}, got)

// Test no loop.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.Collect(beep.Loop2(s, beep.LoopTimes(0)))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}, got)

// Test loop once.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.Collect(beep.Loop(1, s))
got = testtools.Collect(beep.Loop2(s, beep.LoopTimes(1)))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}, got)

// Test loop twice.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.Collect(beep.Loop(2, s))
got = testtools.Collect(beep.Loop2(s, beep.LoopTimes(2)))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}, got)

// Loop indefinitely.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.CollectNum(16, beep.Loop(-1, s))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {0, 0}}, got)

// Test loop from start position.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.Collect(beep.Loop(2, s, beep.LoopStart(2)))
got = testtools.Collect(beep.Loop2(s, beep.LoopTimes(2), beep.LoopStart(2)))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {2, 2}, {3, 3}, {4, 4}, {2, 2}, {3, 3}, {4, 4}}, got)

// Test loop with end position.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.Collect(beep.Loop(2, s, beep.LoopEnd(4)))
got = testtools.Collect(beep.Loop2(s, beep.LoopTimes(2), beep.LoopEnd(4)))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}, got)

// Test loop with start and end position.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.Collect(beep.Loop(2, s, beep.LoopBetween(2, 4)))
got = testtools.Collect(beep.Loop2(s, beep.LoopTimes(2), beep.LoopBetween(2, 4)))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {2, 2}, {3, 3}, {2, 2}, {3, 3}, {4, 4}}, got)

// Loop indefinitely with both start and end position.
s, _ = testtools.NewSequentialDataStreamer(5)
got = testtools.CollectNum(10, beep.Loop(-1, s, beep.LoopBetween(2, 4)))
got = testtools.CollectNum(10, beep.Loop2(s, beep.LoopBetween(2, 4)))
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {2, 2}, {3, 3}, {2, 2}, {3, 3}, {2, 2}, {3, 3}}, got)

// Test streaming from the middle of the loops.
//// Test streaming from the middle of the loops.
s, _ = testtools.NewSequentialDataStreamer(5)
l := beep.Loop(2, s, beep.LoopBetween(2, 4)) // 0, 1, 2, 3, 2, 3, 2, 3
l := beep.Loop2(s, beep.LoopTimes(2), beep.LoopBetween(2, 4)) // 0, 1, 2, 3, 2, 3, 2, 3
// First stream to the middle of a loop.
buf := make([][2]float64, 3)
if n, ok := l.Stream(buf); n != 3 || !ok {
t.Fatalf("want n %d got %d, want ok %t got %t", 5, n, true, ok)
t.Fatalf("want n %d got %d, want ok %t got %t", 3, n, true, ok)
}
assert.Equal(t, [][2]float64{{0, 0}, {1, 1}, {2, 2}}, buf)
// Then stream starting at the middle of the loop.
if n, ok := l.Stream(buf); n != 3 || !ok {
t.Fatalf("want n %d got %d, want ok %t got %t", 5, n, true, ok)
t.Fatalf("want n %d got %d, want ok %t got %t", 3, n, true, ok)
}
assert.Equal(t, [][2]float64{{3, 3}, {2, 2}, {3, 3}}, buf)

// Test error handling in middle of loop.
expectedErr := errors.New("expected error")
s, _ = testtools.NewSequentialDataStreamer(5)
s = testtools.NewDelayedErrorStreamer(s, 5, expectedErr)
l = beep.Loop(3, s, beep.LoopBetween(2, 4)) // 0, 1, 2, 3, 2, 3, 2, 3
l = beep.Loop2(s, beep.LoopTimes(3), beep.LoopBetween(2, 4)) // 0, 1, 2, 3, 2, 3, 2, 3
buf = make([][2]float64, 10)
if n, ok := l.Stream(buf); n != 5 || !ok {
t.Fatalf("want n %d got %d, want ok %t got %t", 5, n, true, ok)
Expand All @@ -103,7 +120,7 @@ func TestLoop(t *testing.T) {
// Test error handling during call to Seek().
s, _ = testtools.NewSequentialDataStreamer(5)
s = testtools.NewSeekErrorStreamer(s, expectedErr)
l = beep.Loop(3, s, beep.LoopBetween(2, 4)) // 0, 1, 2, 3, [error]
l = beep.Loop2(s, beep.LoopTimes(3), beep.LoopBetween(2, 4)) // 0, 1, 2, 3, [error]
buf = make([][2]float64, 10)
if n, ok := l.Stream(buf); n != 4 || !ok {
t.Fatalf("want n %d got %d, want ok %t got %t", 4, n, true, ok)
Expand Down

0 comments on commit dc89327

Please sign in to comment.