diff --git a/bind/api.go b/bind/api.go index af1ad9f..49b46b1 100644 --- a/bind/api.go +++ b/bind/api.go @@ -1,10 +1,11 @@ -// Package bind is for modular binding of mix to audio interface package bind import ( "io" "time" + riff "github.com/youpy/go-riff" + "github.com/go-mix/mix/bind/hardware/null" "github.com/go-mix/mix/bind/opt" "github.com/go-mix/mix/bind/sample" @@ -65,6 +66,19 @@ func LoadWAV(file string) ([]sample.Sample, *spec.AudioSpec) { } } +// LoadWAVFromReader loads WAV data from an io.Reader+io.ReaderAt into a buffer +func LoadWAVFromReader(r riff.RIFFReader) ([]sample.Sample, *spec.AudioSpec) { + switch useLoader { + case opt.InputWAV: + return wav.LoadFromReader(r) + case opt.InputSOX: + // SOX doesn't support io.Reader, fall back to WAV + return wav.LoadFromReader(r) + default: + return make([]sample.Sample, 0), &spec.AudioSpec{} + } +} + // Teardown to close all hardware bindings func Teardown() { switch useOutput { diff --git a/bind/wav/wav.go b/bind/wav/wav.go index c5ccb43..90432c0 100644 --- a/bind/wav/wav.go +++ b/bind/wav/wav.go @@ -5,6 +5,8 @@ import ( "io" "os" + riff "github.com/youpy/go-riff" + "github.com/go-mix/mix/bind/sample" "github.com/go-mix/mix/bind/spec" ) @@ -15,7 +17,12 @@ func Load(path string) (out []sample.Sample, specs *spec.AudioSpec) { panic("File not found: " + path) } file, _ := os.Open(path) - reader, err := NewReader(file) + return LoadFromReader(file) +} + +// LoadFromReader loads WAV data from an io.Reader+io.ReaderAt into memory +func LoadFromReader(r riff.RIFFReader) (out []sample.Sample, specs *spec.AudioSpec) { + reader, err := NewReader(r) if err != nil { panic(err) } diff --git a/bind/wav/wav_test.go b/bind/wav/wav_test.go index e34ebbf..3362542 100644 --- a/bind/wav/wav_test.go +++ b/bind/wav/wav_test.go @@ -2,9 +2,32 @@ package wav import ( + "bytes" + "io/ioutil" "testing" + + "github.com/stretchr/testify/assert" ) func TestLoad(t *testing.T) { // TODO } + +func TestLoadFromReader(t *testing.T) { + // Read a test WAV file + wavData, err := ioutil.ReadFile("../../lib/source/testdata/Signed16bitLittleEndian44100HzMono.wav") + assert.NoError(t, err) + + // Create a bytes.Reader which implements both io.Reader and io.ReaderAt + reader := bytes.NewReader(wavData) + + // Load from reader + samples, spec := LoadFromReader(reader) + + // Verify we got samples and spec + assert.NotNil(t, samples) + assert.NotNil(t, spec) + assert.Greater(t, len(samples), 0) + assert.Greater(t, spec.Freq, float64(0)) + assert.Greater(t, spec.Channels, 0) +} diff --git a/go.sum b/go.sum index e3b8a51..1ea6e19 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,16 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/krig/go-sox v0.0.0-20180617124112-7d2f8ae31981 h1:ir4NRMjkkSP63kAOiFDTQN3dcs0o6c0f1cfpe9V8JT0= github.com/krig/go-sox v0.0.0-20180617124112-7d2f8ae31981/go.mod h1:0uPmTzngejep+JBRxvlmijKKMexuskpMCzWFp+oFzc4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/youpy/go-riff v0.0.0-20131220112943-557d78c11efb h1:RDh7U5Di6o7fblIBe7rVi9KnrcOXUbLwvvLLdP2InSI= github.com/youpy/go-riff v0.0.0-20131220112943-557d78c11efb/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/pkg/profile.v1 v1.3.0 h1:zQjfg5nVj3xAlW4eOk1IjKDjQ+SfQwcQXm+M1IemyYo= gopkg.in/pkg/profile.v1 v1.3.0/go.mod h1:knhHpoyiu3zB9bR/uG9+s8jTFrCOFA3g9Xkh/NCDzJ4= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/lib/source/source.go b/lib/source/source.go index 586d772..ded9051 100644 --- a/lib/source/source.go +++ b/lib/source/source.go @@ -1,9 +1,11 @@ -// Package source models a single audio source package source import ( + "io" "math" + riff "github.com/youpy/go-riff" + "github.com/go-mix/mix/bind" "github.com/go-mix/mix/bind/debug" "github.com/go-mix/mix/bind/sample" @@ -26,6 +28,28 @@ func New(URL string) *Source { return s } +// NewFromReader creates a Source from an io.Reader+io.ReaderAt containing WAV data +func NewFromReader(r riff.RIFFReader) *Source { + s := &Source{ + state: STAGED, + URL: "", + } + s.loadFromReader(r) + return s +} + +// NewFromSamples creates a Source from pre-loaded sample data +func NewFromSamples(samples []sample.Sample, audioSpec *spec.AudioSpec) *Source { + s := &Source{ + state: READY, + URL: "", + sample: samples, + audioSpec: audioSpec, + maxTz: spec.Tz(len(samples)), + } + return s +} + // Source stores a series of Samples in Channels across Time, for audio playback. type Source struct { URL string @@ -103,6 +127,17 @@ func (s *Source) load() { s.state = READY } +func (s *Source) loadFromReader(r riff.RIFFReader) { + s.state = LOADING + s.sample, s.audioSpec = bind.LoadWAVFromReader(r) + if s.audioSpec == nil { + // TODO: handle errors loading from reader + debug.Printf("could not load WAV from reader\n") + } + s.maxTz = spec.Tz(len(s.sample)) + s.state = READY +} + // volume (0 to 1), and pan (-1 to +1) // TODO: ensure implicit panning of source channels! e.g. 2 channels is full left, full right. func volume(channel float64, volume float64, pan float64) sample.Value {