remove audio package in favor of faiface/beep
This commit is contained in:
parent
321c494681
commit
d34b63676d
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue