From 9a1d011a9f457727704c97fd393e3203ca12f3ad Mon Sep 17 00:00:00 2001 From: faiface Date: Wed, 12 Jul 2017 19:37:20 +0200 Subject: [PATCH] audio: wav: simplify code, more DRY, encapsulate decoder type, only export Decode function --- audio/wav/decode.go | 162 ++++++++++++++++++++++++++++++++++++++++++ audio/wav/header.go | 78 -------------------- audio/wav/streamer.go | 154 --------------------------------------- 3 files changed, 162 insertions(+), 232 deletions(-) create mode 100644 audio/wav/decode.go delete mode 100644 audio/wav/header.go delete mode 100644 audio/wav/streamer.go diff --git a/audio/wav/decode.go b/audio/wav/decode.go new file mode 100644 index 0000000..804a705 --- /dev/null +++ b/audio/wav/decode.go @@ -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 +} diff --git a/audio/wav/header.go b/audio/wav/header.go deleted file mode 100644 index dfd9240..0000000 --- a/audio/wav/header.go +++ /dev/null @@ -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 -} diff --git a/audio/wav/streamer.go b/audio/wav/streamer.go deleted file mode 100644 index 16151da..0000000 --- a/audio/wav/streamer.go +++ /dev/null @@ -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 -}