remove audio package in favor of faiface/beep

This commit is contained in:
faiface 2017-07-14 18:34:28 +02:00
parent 4d94c04476
commit 96df742331
8 changed files with 0 additions and 743 deletions

View File

@ -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
})
}

View File

@ -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")
}
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}