audio: wav: simplify code, more DRY, encapsulate decoder type, only export Decode function
This commit is contained in:
parent
55c94b6cc5
commit
502347d987
|
@ -0,0 +1,162 @@
|
||||||
|
package wav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/faiface/pixel/audio"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReadSeekCloser interface {
|
||||||
|
io.Reader
|
||||||
|
io.Seeker
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(rsc ReadSeekCloser) (s audio.StreamSeekCloser, err error) {
|
||||||
|
var d decoder
|
||||||
|
d.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))
|
||||||
|
frameTime := time.Second / time.Duration(s.h.SampleRate)
|
||||||
|
return frameIndex * frameTime
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Second / time.Duration(s.h.SampleRate)))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
var frameWidth int
|
||||||
|
switch {
|
||||||
|
case s.h.BitsPerSample == 8 && s.h.NumChans == 1:
|
||||||
|
frameWidth = 1
|
||||||
|
case s.h.BitsPerSample == 8 && s.h.NumChans >= 2:
|
||||||
|
frameWidth = int(s.h.NumChans)
|
||||||
|
case s.h.BitsPerSample == 16 && s.h.NumChans == 1:
|
||||||
|
frameWidth = 2
|
||||||
|
case s.h.BitsPerSample == 16 && s.h.NumChans >= 2:
|
||||||
|
frameWidth = int(s.h.NumChans) * 2
|
||||||
|
}
|
||||||
|
p := make([]byte, len(samples)*frameWidth)
|
||||||
|
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-frameWidth; i, j = i+frameWidth, 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-frameWidth; i, j = i+frameWidth, 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-frameWidth; i, j = i+frameWidth, 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-frameWidth; i, j = i+frameWidth, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n / frameWidth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *decoder) Close() error {
|
||||||
|
err := s.rsc.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "wav")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,78 +0,0 @@
|
||||||
package wav
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type header struct {
|
|
||||||
fileSize int32
|
|
||||||
formatSize int32
|
|
||||||
formatType int16
|
|
||||||
numChans int16
|
|
||||||
sampleRate int32
|
|
||||||
byteRate int32
|
|
||||||
bytesPerFrame int16
|
|
||||||
bitsPerSample int16
|
|
||||||
dataSize int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func readHeader(r io.Reader) (header, error) {
|
|
||||||
var (
|
|
||||||
h header
|
|
||||||
er errReader
|
|
||||||
)
|
|
||||||
err := er.
|
|
||||||
ReadString(r, "RIFF", errors.New("missing RIFF at the beginning")).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.fileSize).
|
|
||||||
ReadString(r, "WAVE", errors.New("unsupported file type")).
|
|
||||||
ReadString(r, "fmt\x00", errors.New("missing format chunk marker")).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.formatSize).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.formatType).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.numChans).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.sampleRate).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.byteRate).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.bytesPerFrame).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.bitsPerSample).
|
|
||||||
ReadString(r, "data", errors.New("missing data chunk marker")).
|
|
||||||
ReadBinary(r, binary.LittleEndian, &h.dataSize).
|
|
||||||
Err()
|
|
||||||
return h, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type errReader struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errReader) ReadString(r io.Reader, s string, notThereErr error) *errReader {
|
|
||||||
if e.err != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
buf := make([]byte, len(s))
|
|
||||||
_, err := r.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
e.err = errors.Wrap(err, "error while reading header")
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
if string(buf) != s {
|
|
||||||
e.err = errors.Wrap(err, "invalid header")
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errReader) ReadBinary(r io.Reader, order binary.ByteOrder, data interface{}) *errReader {
|
|
||||||
if e.err != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
err := binary.Read(r, order, data)
|
|
||||||
if err != nil {
|
|
||||||
e.err = errors.Wrap(err, "invalid header")
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errReader) Err() error {
|
|
||||||
return e.err
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
package wav
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/faiface/pixel/audio"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReadSeekCloser interface {
|
|
||||||
io.Reader
|
|
||||||
io.Seeker
|
|
||||||
io.Closer
|
|
||||||
}
|
|
||||||
|
|
||||||
type Streamer struct {
|
|
||||||
rsc ReadSeekCloser
|
|
||||||
h header
|
|
||||||
pos int32
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ audio.StreamSeekCloser = (*Streamer)(nil)
|
|
||||||
|
|
||||||
func NewStreamer(rsc ReadSeekCloser) (s *Streamer, err error) {
|
|
||||||
var (
|
|
||||||
d Streamer
|
|
||||||
herr error
|
|
||||||
)
|
|
||||||
d.rsc = rsc
|
|
||||||
defer func() { // hacky way to always close rsc if an error occured
|
|
||||||
if err != nil {
|
|
||||||
d.rsc.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
d.h, herr = readHeader(rsc)
|
|
||||||
if herr != nil {
|
|
||||||
return nil, errors.Wrap(herr, "wav")
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Streamer) Err() error {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Streamer) 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 *Streamer) Position() time.Duration {
|
|
||||||
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) 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.Second / time.Duration(s.h.sampleRate)))
|
|
||||||
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 *Streamer) Stream(samples [][2]float64) (n int, ok bool) {
|
|
||||||
if s.pos >= s.h.dataSize {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case s.h.bitsPerSample == 8 && s.h.numChans == 1:
|
|
||||||
width := 1
|
|
||||||
p := make([]byte, len(samples)*width)
|
|
||||||
n, err := s.rsc.Read(p)
|
|
||||||
for i, j := 0, 0; i < n-width; i, j = i+width, j+1 {
|
|
||||||
val := float64(p[i])/(1<<8-1)*2 - 1
|
|
||||||
samples[j][0] = val
|
|
||||||
samples[j][1] = val
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
s.err = err
|
|
||||||
}
|
|
||||||
s.pos += int32(n)
|
|
||||||
return n / width, true
|
|
||||||
case s.h.bitsPerSample == 8 && s.h.numChans >= 2:
|
|
||||||
width := int(s.h.numChans)
|
|
||||||
p := make([]byte, len(samples)*width)
|
|
||||||
n, err := s.rsc.Read(p)
|
|
||||||
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][1] = float64(p[i+1])/(1<<8-1)*2 - 1
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
s.err = err
|
|
||||||
}
|
|
||||||
s.pos += int32(n)
|
|
||||||
return n / width, true
|
|
||||||
case s.h.bitsPerSample == 16 && s.h.numChans == 1:
|
|
||||||
width := 2
|
|
||||||
p := make([]byte, len(samples)*width)
|
|
||||||
n, err := s.rsc.Read(p)
|
|
||||||
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)
|
|
||||||
samples[j][0] = val
|
|
||||||
samples[j][1] = val
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
s.err = err
|
|
||||||
}
|
|
||||||
s.pos += int32(n)
|
|
||||||
return n / width, true
|
|
||||||
case s.h.bitsPerSample == 16 && s.h.numChans >= 2:
|
|
||||||
width := int(s.h.numChans) * 2
|
|
||||||
p := make([]byte, len(samples)*width)
|
|
||||||
n, err := s.rsc.Read(p)
|
|
||||||
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][1] = float64(int16(p[i+2])+int16(p[i+3])*(1<<8)) / (1<<15 - 1)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
s.err = err
|
|
||||||
}
|
|
||||||
s.pos += int32(n)
|
|
||||||
return n / width, true
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Streamer) Close() error {
|
|
||||||
err := s.rsc.Close()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "wav")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue