diff --git a/audio/compositors.go b/audio/compositors.go deleted file mode 100644 index 2d2b89b..0000000 --- a/audio/compositors.go +++ /dev/null @@ -1,102 +0,0 @@ -package audio - -import ( - "math" - "time" -) - -// Take returns a Streamer which streams s for at most d duration. -// -// The returned Streamer propagates s's errors throught Err. -func Take(d time.Duration, s Streamer) Streamer { - return &take{ - s: s, - currSample: 0, - numSamples: int(math.Ceil(d.Seconds() * SampleRate)), - } -} - -type take struct { - s Streamer - currSample int - numSamples int -} - -func (t *take) Stream(samples [][2]float64) (n int, ok bool) { - if t.currSample >= t.numSamples { - return 0, false - } - toStream := t.numSamples - t.currSample - if len(samples) < toStream { - toStream = len(samples) - } - n, ok = t.s.Stream(samples[:toStream]) - t.currSample += n - return n, ok -} - -func (t *take) Err() error { - return t.s.Err() -} - -// Seq takes zero or more Streamers and returns a Streamer which streams them one by one without pauses. -// -// Seq does not propagate errors from the Streamers. -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 - }) -} - -// Mix takes zero or more Streamers and returns a Streamer which streames them mixed together. -// -// Mix does not propagate errors from the Streamers. -func Mix(s ...Streamer) Streamer { - return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { - var tmp [512][2]float64 - - for len(samples) > 0 { - toStream := len(tmp) - if toStream > len(samples) { - toStream = len(samples) - } - - // clear the samples - for i := range samples[:toStream] { - samples[i] = [2]float64{} - } - - snMax := 0 // max number of streamed samples in this iteration - for _, st := range s { - // mix the stream - sn, sok := st.Stream(tmp[:toStream]) - if sn > snMax { - snMax = sn - } - ok = ok || sok - - for i := range tmp[:sn] { - samples[i][0] += tmp[i][0] - samples[i][1] += tmp[i][1] - } - } - - n += snMax - if snMax < len(tmp) { - break - } - samples = samples[snMax:] - } - - return n, ok - }) -} diff --git a/audio/compositors_test.go b/audio/compositors_test.go deleted file mode 100644 index 6842d89..0000000 --- a/audio/compositors_test.go +++ /dev/null @@ -1,104 +0,0 @@ -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") - } -} - -func TestMix(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))) - for j := range data { - if j >= len(want) { - want = append(want, data[j]) - continue - } - want[j][0] += data[j][0] - want[j][1] += data[j][1] - } - } - - got := collect(audio.Mix(s...)) - - if !reflect.DeepEqual(want, got) { - t.Error("Mix not working correctly") - } -} diff --git a/audio/ctrl.go b/audio/ctrl.go deleted file mode 100644 index 54d1b30..0000000 --- a/audio/ctrl.go +++ /dev/null @@ -1,66 +0,0 @@ -package audio - -import "time" - -// Ctrl allows for pausing and tracking a Streamer. -// -// Wrap a Streamer in a Ctrl. -// -// ctrl := &audio.Ctrl{Streamer: s} -// -// Then, we can pause the streaming (this will cause Ctrl to stream silence). -// -// ctrl.Paused = true -// -// And we can check how much has already been streamed. Position is not incremented when the Ctrl is -// paused. -// -// fmt.Println(ctrl.Position) -// -// To completely stop a Ctrl before the wrapped Streamer is drained, just set the wrapped Streamer -// to nil. -// -// ctrl.Streamer = nil -// -// If you're playing a Streamer wrapped in a Ctrl through the speaker, you need to lock and unlock -// the speaker when modifying the Ctrl to avoid race conditions. -// -// speaker.Play(ctrl) -// // ... -// speaker.Lock() -// ctrl.Paused = true -// speaker.Unlock() -// // ... -// speaker.Lock() -// fmt.Println(ctrl.Position) -// speaker.Unlock() -type Ctrl struct { - Streamer Streamer - Paused bool - Position time.Duration -} - -// Stream streams the wrapped Streamer, if not nil. If the Streamer is nil, Ctrl acts as drained. -// When paused, Ctrl streams silence. -func (c *Ctrl) Stream(samples [][2]float64) (n int, ok bool) { - if c.Streamer == nil { - return 0, false - } - if c.Paused { - for i := range samples { - samples[i] = [2]float64{} - } - return len(samples), true - } - n, ok = c.Streamer.Stream(samples) - c.Position += time.Duration(n) * time.Second / time.Duration(SampleRate) - return n, ok -} - -// Err returns the error of the wrapped Streamer, if not nil. -func (c *Ctrl) Err() error { - if c.Streamer == nil { - return nil - } - return c.Err() -} diff --git a/audio/effects.go b/audio/effects.go deleted file mode 100644 index 9a86478..0000000 --- a/audio/effects.go +++ /dev/null @@ -1,26 +0,0 @@ -package audio - -// Gain amplifies the wrapped Streamer. The output of the wrapped Streamer gets multiplied by -// 1+Gain. -// -// Note that gain is not equivalent to the human perception of volume. Human perception of volume is -// roughly exponential, while gain only amplifies linearly. -type Gain struct { - Streamer Streamer - Gain float64 -} - -// Stream streams the wrapped Streamer amplified by Gain. -func (g *Gain) Stream(samples [][2]float64) (n int, ok bool) { - n, ok = g.Streamer.Stream(samples) - for i := range samples[:n] { - samples[i][0] *= 1 + g.Gain - samples[i][1] *= 1 + g.Gain - } - return n, ok -} - -// Err propagates the wrapped Streamer's errors. -func (g *Gain) Err() error { - return g.Streamer.Err() -} diff --git a/audio/interface.go b/audio/interface.go deleted file mode 100644 index ad05342..0000000 --- a/audio/interface.go +++ /dev/null @@ -1,112 +0,0 @@ -package audio - -import "time" - -// 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 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 { - // Stream copies at most len(samples) next audio samples to the samples slice. - // - // The sample rate of the samples is specified by the global SampleRate variable/constant. - // The value at samples[i][0] is the value of the left channel of the i-th sample. - // Similarly, samples[i][1] is the value of the right channel of the i-th sample. - // - // Stream returns the number of streamed samples. If the Streamer is drained and no more - // 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: - // - // 1. n == len(samples) && ok - // - // Stream streamed all of the requested samples. Cases 1, 2 and 3 may occur in the following - // calls. - // - // 2. 0 < n && n < len(samples) && ok - // - // Stream streamed n samples and drained the Streamer. Only case 3 may occur in the - // following calls. If Err return a non-nil error, only this case is valid. - // - // 3. n == 0 && !ok - // - // The Streamer is drained and no more samples will come. Only this case may occur in the - // following calls. - Stream(samples [][2]float64) (n int, ok bool) - - // Err returns an error which occured during streaming. If no error occured, nil is - // returned. - // - // When an error occurs, Streamer must become drained and Stream must return 0, false - // forever. - // - // The reason why Stream doesn't return an error is that it dramatically simplifies - // programming with Streamer. It's not very important to catch the error right when it - // happens. - Err() error -} - -// StreamSeeker is a finite duration Streamer which supports seeking to an arbitrary position. -type StreamSeeker interface { - Streamer - - // Duration returns the total duration of the Streamer. - Duration() time.Duration - - // Position returns the current position of the Streamer. This value is between 0 and the - // total duration. - Position() time.Duration - - // Seek sets the position of the Streamer to the provided value. - // - // If an error occurs during seeking, the position remains unchanged. This error will not be - // returned through the Streamer's Err method. - Seek(d time.Duration) error -} - -// StreamCloser is a Streamer streaming from a resource which needs to be released, such as a file -// or a network connection. -type StreamCloser interface { - Streamer - - // Close closes the Streamer and releases it's resources. Streamer will no longer stream any - // samples. - Close() error -} - -// StreamSeekCloser is a union of StreamSeeker and StreamCloser. -type StreamSeekCloser interface { - Streamer - Duration() time.Duration - Position() time.Duration - Seek(d time.Duration) error - Close() error -} - -// 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) -} - -// Err always returns nil. -func (sf StreamerFunc) Err() error { - return nil -} diff --git a/audio/mixer.go b/audio/mixer.go deleted file mode 100644 index 1de0fa5..0000000 --- a/audio/mixer.go +++ /dev/null @@ -1,65 +0,0 @@ -package audio - -// Mixer allows for dynamic mixing of arbitrary number of Streamers. Mixer automatically removes -// drained Streamers. Mixer's stream never drains, when empty, Mixer streams silence. -type Mixer struct { - streamers []Streamer -} - -// Len returns the number of Streamers currently playing in the Mixer. -func (m *Mixer) Len() int { - return len(m.streamers) -} - -// Play adds Streamers to the Mixer. -func (m *Mixer) Play(s ...Streamer) { - m.streamers = append(m.streamers, s...) -} - -// Stream streams all Streamers currently in the Mixer mixed together. This method always returns -// len(samples), true. If there are no Streamers available, this methods streams silence. -func (m *Mixer) Stream(samples [][2]float64) (n int, ok bool) { - var tmp [512][2]float64 - - for len(samples) > 0 { - toStream := len(tmp) - if toStream > len(samples) { - toStream = len(samples) - } - - // clear the samples - for i := range samples[:toStream] { - samples[i] = [2]float64{} - } - - for si := 0; si < len(m.streamers); si++ { - // mix the stream - sn, sok := m.streamers[si].Stream(tmp[:toStream]) - for i := range tmp[:sn] { - samples[i][0] += tmp[i][0] - samples[i][1] += tmp[i][1] - } - if !sok { - // remove drained streamer - sj := len(m.streamers) - 1 - m.streamers[si], m.streamers[sj] = m.streamers[sj], m.streamers[si] - m.streamers = m.streamers[:sj] - si-- - } - } - - samples = samples[toStream:] - n += toStream - } - - return n, true -} - -// Err always returns nil for Mixer. -// -// There are two reasons. The first one is that erroring Streamers are immediately drained and -// removed from the Mixer. The second one is that one Streamer shouldn't break the whole Mixer and -// you should handle the errors right where they can happen. -func (m *Mixer) Err() error { - return nil -} diff --git a/audio/speaker/speaker.go b/audio/speaker/speaker.go deleted file mode 100644 index dbb153c..0000000 --- a/audio/speaker/speaker.go +++ /dev/null @@ -1,110 +0,0 @@ -package speaker - -import ( - "math" - "sync" - "time" - - "github.com/faiface/pixel/audio" - "github.com/hajimehoshi/oto" - "github.com/pkg/errors" -) - -var ( - mu sync.Mutex - mixer audio.Mixer - samples [][2]float64 - buf []byte - player *oto.Player - done chan struct{} -) - -// Init initializes audio playback through speaker. Must be called before using this package. The -// value of audio.SampleRate must be set (or left to the default) before calling this function. -// -// The bufferSize argument specifies the length of the speaker's buffer. Bigger bufferSize means -// lower CPU usage and more reliable playback. Lower bufferSize means better responsiveness and less -// delay. -func Init(bufferSize time.Duration) error { - mu.Lock() - defer mu.Unlock() - - if player != nil { - done <- struct{}{} - player.Close() - } - - mixer = audio.Mixer{} - - numSamples := int(math.Ceil(bufferSize.Seconds() * audio.SampleRate)) - numBytes := numSamples * 4 - - samples = make([][2]float64, numSamples) - buf = make([]byte, numBytes) - - var err error - player, err = oto.NewPlayer(int(audio.SampleRate), 2, 2, numBytes) - if err != nil { - return errors.Wrap(err, "failed to initialize speaker") - } - - done = make(chan struct{}) - - go func() { - for { - select { - default: - update() - case <-done: - return - } - } - }() - - return nil -} - -// Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers. Lock -// if you want to modify any currently playing Streamers to avoid race conditions. -func Lock() { - mu.Lock() -} - -// Unlock unlocks the speaker. Call after modifying any currently playing Streamer. -func Unlock() { - mu.Unlock() -} - -// Play starts playing all provided Streamers through the speaker. -func Play(s ...audio.Streamer) { - mu.Lock() - mixer.Play(s...) - mu.Unlock() -} - -// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the -// data is sent and started playing. -func update() { - mu.Lock() - mixer.Stream(samples) - mu.Unlock() - - for i := range samples { - for c := range samples[i] { - val := samples[i][c] - if val < -1 { - val = -1 - } - if val > +1 { - val = +1 - } - valInt16 := int16(val * (1<<15 - 1)) - low := byte(valInt16) - high := byte(valInt16 >> 8) - buf[i*4+c*2+0] = low - buf[i*4+c*2+1] = high - } - } - - player.Write(buf) -} diff --git a/audio/wav/decode.go b/audio/wav/decode.go deleted file mode 100644 index eb0502d..0000000 --- a/audio/wav/decode.go +++ /dev/null @@ -1,158 +0,0 @@ -// Package wav implements audio data decoding in WAVE format through an audio.StreamSeekCloser. -package wav - -import ( - "encoding/binary" - "fmt" - "io" - "time" - - "github.com/faiface/pixel/audio" - "github.com/pkg/errors" -) - -// ReadSeekCloser is a union of io.Reader, io.Seeker and io.Closer. -type ReadSeekCloser interface { - io.Reader - io.Seeker - io.Closer -} - -// Decode takes a ReadSeekCloser containing audio data in WAVE format and returns a -// StreamSeekCloser, which streams that audio. -// -// Do not close the supplied ReadSeekCloser, instead, use the Close method of the returned -// StreamSeekCloser when you want to release the resources. -func Decode(rsc ReadSeekCloser) (s audio.StreamSeekCloser, err error) { - d := decoder{rsc: rsc} - defer func() { // hacky way to always close rsc if an error occured - if err != nil { - d.rsc.Close() - } - }() - herr := binary.Read(rsc, binary.LittleEndian, &d.h) - if herr != nil { - return nil, errors.Wrap(herr, "wav") - } - if string(d.h.RiffMark[:]) != "RIFF" { - return nil, errors.New("wav: missing RIFF at the beginning") - } - if string(d.h.WaveMark[:]) != "WAVE" { - return nil, errors.New("wav: unsupported file type") - } - if string(d.h.FmtMark[:]) != "fmt " { - return nil, errors.New("wav: missing format chunk marker") - } - if string(d.h.DataMark[:]) != "data" { - return nil, errors.New("wav: missing data chunk marker") - } - if d.h.FormatType != 1 { - return nil, errors.New("wav: unsupported format type") - } - if d.h.NumChans <= 0 { - return nil, errors.New("wav: invalid number of channels (less than 1)") - } - if d.h.BitsPerSample != 8 && d.h.BitsPerSample != 16 { - return nil, errors.New("wav: unsupported number of bits per sample, 8 or 16 are supported") - } - return &d, nil -} - -type header struct { - RiffMark [4]byte - FileSize int32 - WaveMark [4]byte - FmtMark [4]byte - FormatSize int32 - FormatType int16 - NumChans int16 - SampleRate int32 - ByteRate int32 - BytesPerFrame int16 - BitsPerSample int16 - DataMark [4]byte - DataSize int32 -} - -type decoder struct { - rsc ReadSeekCloser - h header - pos int32 - err error -} - -func (s *decoder) Err() error { - return s.err -} - -func (s *decoder) Duration() time.Duration { - numBytes := time.Duration(s.h.DataSize) - perFrame := time.Duration(s.h.BytesPerFrame) - sampRate := time.Duration(s.h.SampleRate) - return numBytes / perFrame * time.Second / sampRate -} - -func (s *decoder) Position() time.Duration { - frameIndex := time.Duration(s.pos / int32(s.h.BytesPerFrame)) - return frameIndex * time.Second / time.Duration(s.h.SampleRate) -} - -func (s *decoder) Seek(d time.Duration) error { - if d < 0 || s.Duration() < d { - return fmt.Errorf("wav: seek duration %v out of range [%v, %v]", d, 0, s.Duration()) - } - frame := int32(d * time.Duration(s.h.SampleRate) / time.Second) - pos := frame * int32(s.h.BytesPerFrame) - _, err := s.rsc.Seek(int64(pos)+44, io.SeekStart) // 44 is the size of the header - if err != nil { - return errors.Wrap(err, "wav: seek error") - } - s.pos = pos - return nil -} - -func (s *decoder) Stream(samples [][2]float64) (n int, ok bool) { - if s.err != nil || s.pos >= s.h.DataSize { - return 0, false - } - bytesPerFrame := int(s.h.BytesPerFrame) - p := make([]byte, len(samples)*bytesPerFrame) - n, err := s.rsc.Read(p) - if err != nil { - s.err = err - } - switch { - case s.h.BitsPerSample == 8 && s.h.NumChans == 1: - for i, j := 0, 0; i <= n-bytesPerFrame; i, j = i+bytesPerFrame, j+1 { - val := float64(p[i])/(1<<8-1)*2 - 1 - samples[j][0] = val - samples[j][1] = val - } - case s.h.BitsPerSample == 8 && s.h.NumChans >= 2: - for i, j := 0, 0; i <= n-bytesPerFrame; i, j = i+bytesPerFrame, j+1 { - samples[j][0] = float64(p[i+0])/(1<<8-1)*2 - 1 - samples[j][1] = float64(p[i+1])/(1<<8-1)*2 - 1 - } - case s.h.BitsPerSample == 16 && s.h.NumChans == 1: - for i, j := 0, 0; i <= n-bytesPerFrame; i, j = i+bytesPerFrame, j+1 { - val := float64(int16(p[i+0])+int16(p[i+1])*(1<<8)) / (1<<15 - 1) - samples[j][0] = val - samples[j][1] = val - } - case s.h.BitsPerSample == 16 && s.h.NumChans >= 2: - for i, j := 0, 0; i <= n-bytesPerFrame; i, j = i+bytesPerFrame, j+1 { - samples[j][0] = float64(int16(p[i+0])+int16(p[i+1])*(1<<8)) / (1<<15 - 1) - samples[j][1] = float64(int16(p[i+2])+int16(p[i+3])*(1<<8)) / (1<<15 - 1) - } - } - s.pos += int32(n) - return n / bytesPerFrame, true -} - -func (s *decoder) Close() error { - err := s.rsc.Close() - if err != nil { - return errors.Wrap(err, "wav") - } - return nil -}