diff --git a/.travis.yml b/.travis.yml index bef75a2..a02ff15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ addons: - libxinerama-dev - libxcursor-dev - libxi-dev + - libasound2-dev go: - 1.8 - 1.7.4 diff --git a/audio/compositors.go b/audio/compositors.go new file mode 100644 index 0000000..ceed138 --- /dev/null +++ b/audio/compositors.go @@ -0,0 +1,40 @@ +package audio + +import ( + "math" + "time" +) + +// Take returns a Streamer which streams s for at most d duration. +func Take(d time.Duration, s Streamer) Streamer { + currSample := 0 + numSamples := int(math.Ceil(d.Seconds() * SampleRate)) + return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + if currSample >= numSamples { + return 0, false + } + toStream := numSamples - currSample + if len(samples) < toStream { + toStream = len(samples) + } + sn, sok := s.Stream(samples[:toStream]) + currSample += sn + return sn, sok + }) +} + +// Seq takes zero or more Streamers and returns a Streamer which streams them one by one without pauses. +func Seq(s ...Streamer) Streamer { + i := 0 + return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + for i < len(s) && len(samples) > 0 { + sn, sok := s[i].Stream(samples) + samples = samples[sn:] + n, ok = n+sn, ok || sok + if !sok { + i++ + } + } + return n, ok + }) +} diff --git a/audio/compositors_test.go b/audio/compositors_test.go new file mode 100644 index 0000000..450430b --- /dev/null +++ b/audio/compositors_test.go @@ -0,0 +1,79 @@ +package audio_test + +import ( + "math" + "math/rand" + "reflect" + "testing" + "time" + + "github.com/faiface/pixel/audio" +) + +// randomDataStreamer generates random samples of duration d and returns a Streamer which streams +// them and the data itself. +func randomDataStreamer(d time.Duration) (s audio.Streamer, data [][2]float64) { + numSamples := int(math.Ceil(d.Seconds() * audio.SampleRate)) + data = make([][2]float64, numSamples) + for i := range data { + data[i][0] = rand.Float64()*2 - 1 + data[i][1] = rand.Float64()*2 - 1 + } + return audio.StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + if len(data) == 0 { + return 0, false + } + n = copy(samples, data) + data = data[n:] + return n, true + }), data +} + +// collect drains Streamer s and returns all of the samples it streamed. +func collect(s audio.Streamer) [][2]float64 { + var ( + result [][2]float64 + buf [512][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 := time.Nanosecond * time.Duration(1e8+rand.Intn(1e9)) + s, data := randomDataStreamer(total) + d := time.Nanosecond * time.Duration(rand.Int63n(total.Nanoseconds())) + numSamples := int(math.Ceil(d.Seconds() * audio.SampleRate)) + + want := data[:numSamples] + got := collect(audio.Take(d, s)) + + if !reflect.DeepEqual(want, got) { + t.Error("Take not working correctly") + } + } +} + +func TestSeq(t *testing.T) { + var ( + s = make([]audio.Streamer, 7) + want [][2]float64 + ) + for i := range s { + var data [][2]float64 + s[i], data = randomDataStreamer(time.Nanosecond * time.Duration(1e8+rand.Intn(1e9))) + want = append(want, data...) + } + + got := collect(audio.Seq(s...)) + + if !reflect.DeepEqual(want, got) { + t.Error("Seq not working correctly") + } +} diff --git a/audio/interface.go b/audio/interface.go index f7a0a81..d426beb 100644 --- a/audio/interface.go +++ b/audio/interface.go @@ -2,9 +2,9 @@ package audio // SampleRate is the number of audio samples a Streamer should produce per one second of audio. // -// This value should be set at most once before using audio package. It is safe to rely on the fact, -// that this value does not change during runtime. -var SampleRate = 48000 +// This value should be set at most once before using audio package. It is safe to assume that this +// value does not change during runtime. +var SampleRate float64 = 48000 // Streamer is able to stream a finite or infinite sequence of audio samples. type Streamer interface { @@ -36,3 +36,22 @@ type Streamer interface { // following calls. Stream(samples [][2]float64) (n int, ok bool) } + +// StreamerFunc is a Streamer created by simply wrapping a streaming function (usually a closure, +// which encloses a time tracking variable). This sometimes simplifies creating new streamers. +// +// Example: +// +// noise := StreamerFunc(func(samples [][2]float64) (n int, ok bool) { +// for i := range samples { +// samples[i][0] = rand.Float64()*2 - 1 +// samples[i][1] = rand.Float64()*2 - 1 +// } +// return len(samples), true +// }) +type StreamerFunc func(samples [][2]float64) (n int, ok bool) + +// Stream calls the wrapped streaming function. +func (sf StreamerFunc) Stream(samples [][2]float64) (n int, ok bool) { + return sf(samples) +}