remove audio package in favor of faiface/beep
This commit is contained in:
parent
4d94c04476
commit
96df742331
|
@ -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