audio: wav: add Seek and Position

This commit is contained in:
faiface 2017-07-11 20:00:29 +02:00
parent 73a3f65adb
commit f3647fc451
1 changed files with 67 additions and 40 deletions

View File

@ -7,115 +7,142 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type ReadSeekCloser interface {
io.Reader
io.Seeker
io.Closer
}
type Streamer struct { type Streamer struct {
rc io.ReadCloser rsc ReadSeekCloser
h header h header
pos int32 pos int32
err error err error
} }
func NewStreamer(rc io.ReadCloser) (*Streamer, error) { func NewStreamer(rsc ReadSeekCloser) (s *Streamer, err error) {
var ( var (
d Streamer d Streamer
err error herr error
) )
d.rc = rc d.rsc = rsc
d.h, err = readHeader(rc) defer func() { // hacky way to always close rsc if an error occured
if err != nil { if err != nil {
rc.Close() d.rsc.Close()
return nil, errors.Wrap(err, "wav") }
}()
d.h, herr = readHeader(rsc)
if herr != nil {
return nil, errors.Wrap(herr, "wav")
} }
if d.h.formatType != 1 { if d.h.formatType != 1 {
rc.Close()
return nil, errors.New("wav: unsupported format type") return nil, errors.New("wav: unsupported format type")
} }
if d.h.numChans <= 0 { if d.h.numChans <= 0 {
rc.Close()
return nil, errors.New("wav: invalid number of channels (less than 1)") return nil, errors.New("wav: invalid number of channels (less than 1)")
} }
if d.h.bitsPerSample != 8 && d.h.bitsPerSample != 16 { if d.h.bitsPerSample != 8 && d.h.bitsPerSample != 16 {
rc.Close()
return nil, errors.New("wav: unsupported number of bits per sample, 8 or 16 are supported") return nil, errors.New("wav: unsupported number of bits per sample, 8 or 16 are supported")
} }
return &d, nil return &d, nil
} }
func (d *Streamer) Err() error { func (s *Streamer) Err() error {
return d.err return s.err
} }
func (d *Streamer) Duration() time.Duration { func (s *Streamer) Duration() time.Duration {
numBytes := time.Duration(d.h.dataSize) numBytes := time.Duration(s.h.dataSize)
perFrame := time.Duration(d.h.bytesPerFrame) perFrame := time.Duration(s.h.bytesPerFrame)
sampRate := time.Duration(d.h.sampleRate) sampRate := time.Duration(s.h.sampleRate)
return numBytes / perFrame * time.Second / sampRate return numBytes / perFrame * time.Second / sampRate
} }
func (d *Streamer) Stream(samples [][2]float64) (n int, ok bool) { func (s *Streamer) Position() time.Duration {
if d.pos >= d.h.dataSize { frameIndex := time.Duration(s.pos / int32(s.h.bytesPerFrame))
frameTime := time.Second / time.Duration(s.h.sampleRate)
return frameIndex * frameTime
}
func (s *Streamer) Seek(d time.Duration) {
if d < 0 || s.Duration() < d {
panic("wav: seek duration out of range")
}
frame := int32(d / (time.Second / time.Duration(s.h.sampleRate)))
pos := frame * int32(s.h.bytesPerFrame)
_, err := s.rsc.Seek(int64(pos), io.SeekStart)
if err != nil {
s.err = err
return
}
s.pos = pos
}
func (s *Streamer) Stream(samples [][2]float64) (n int, ok bool) {
if s.pos >= s.h.dataSize {
return 0, false return 0, false
} }
switch { switch {
case d.h.bitsPerSample == 8 && d.h.numChans == 1: case s.h.bitsPerSample == 8 && s.h.numChans == 1:
width := 1 width := 1
p := make([]byte, len(samples)*width) p := make([]byte, len(samples)*width)
n, err := d.rc.Read(p) n, err := s.rsc.Read(p)
for i, j := 0, 0; i < n-width; i, j = i+width, j+1 { for i, j := 0, 0; i < n-width; i, j = i+width, j+1 {
val := float64(p[i])/(1<<8-1)*2 - 1 val := float64(p[i])/(1<<8-1)*2 - 1
samples[j][0] = val samples[j][0] = val
samples[j][1] = val samples[j][1] = val
} }
if err != nil { if err != nil {
d.err = err s.err = err
} }
d.pos += int32(n) s.pos += int32(n)
return n / width, true return n / width, true
case d.h.bitsPerSample == 8 && d.h.numChans >= 2: case s.h.bitsPerSample == 8 && s.h.numChans >= 2:
width := int(d.h.numChans) width := int(s.h.numChans)
p := make([]byte, len(samples)*width) p := make([]byte, len(samples)*width)
n, err := d.rc.Read(p) n, err := s.rsc.Read(p)
for i, j := 0, 0; i < n-width; i, j = i+width, j+1 { for i, j := 0, 0; i < n-width; i, j = i+width, j+1 {
samples[j][0] = float64(p[i+0])/(1<<8-1)*2 - 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 samples[j][1] = float64(p[i+1])/(1<<8-1)*2 - 1
} }
if err != nil { if err != nil {
d.err = err s.err = err
} }
d.pos += int32(n) s.pos += int32(n)
return n / width, true return n / width, true
case d.h.bitsPerSample == 16 && d.h.numChans == 1: case s.h.bitsPerSample == 16 && s.h.numChans == 1:
width := 2 width := 2
p := make([]byte, len(samples)*width) p := make([]byte, len(samples)*width)
n, err := d.rc.Read(p) n, err := s.rsc.Read(p)
for i, j := 0, 0; i < n-width; i, j = i+width, j+1 { for i, j := 0, 0; i < n-width; i, j = i+width, j+1 {
val := float64(int16(p[i+0])+int16(p[i+1])*(1<<8)) / (1<<15 - 1) val := float64(int16(p[i+0])+int16(p[i+1])*(1<<8)) / (1<<15 - 1)
samples[j][0] = val samples[j][0] = val
samples[j][1] = val samples[j][1] = val
} }
if err != nil { if err != nil {
d.err = err s.err = err
} }
d.pos += int32(n) s.pos += int32(n)
return n / width, true return n / width, true
case d.h.bitsPerSample == 16 && d.h.numChans >= 2: case s.h.bitsPerSample == 16 && s.h.numChans >= 2:
width := int(d.h.numChans) * 2 width := int(s.h.numChans) * 2
p := make([]byte, len(samples)*width) p := make([]byte, len(samples)*width)
n, err := d.rc.Read(p) n, err := s.rsc.Read(p)
for i, j := 0, 0; i <= n-width; i, j = i+width, j+1 { for i, j := 0, 0; i <= n-width; i, j = i+width, j+1 {
samples[j][0] = float64(int16(p[i+0])+int16(p[i+1])*(1<<8)) / (1<<15 - 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) samples[j][1] = float64(int16(p[i+2])+int16(p[i+3])*(1<<8)) / (1<<15 - 1)
} }
if err != nil { if err != nil {
d.err = err s.err = err
} }
d.pos += int32(n) s.pos += int32(n)
return n / width, true return n / width, true
} }
panic("unreachable") panic("unreachable")
} }
func (d *Streamer) Close() error { func (s *Streamer) Close() error {
err := d.rc.Close() err := s.rsc.Close()
if err != nil { if err != nil {
return errors.Wrap(err, "wav") return errors.Wrap(err, "wav")
} }