├── .gitignore ├── LICENSE ├── README.md ├── errors.go ├── header.go ├── manip.go ├── reader.go ├── reader_test.go ├── sound.go ├── wav-crop └── main.go ├── wav-duration └── main.go ├── wav-gradient └── main.go ├── wav-join └── main.go ├── wav-overlay └── main.go ├── wav-tone └── main.go └── wav-volume └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Alexander Nichol 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wav 2 | 3 | **wav** is a dead-simple API for dealing with WAV audio files. 4 | 5 | # Examples 6 | 7 | I have created many examples in the form of command line utilities. These utilities are as follows: 8 | 9 | * [wav-crop](wav-crop/main.go) - cut a WAV file 10 | * [wav-duration](wav-duration/main.go) - get the duration of a WAV file 11 | * [wav-gradient](wav-gradient/main.go) - fade in/out a WAV file 12 | * [wav-overlay](wav-overlay/main.go) - play one WAV file on top of another one 13 | * [wav-tone](wav-tone/main.go) - generate a sinusoidal tone at a given frequency 14 | * [wav-volume](wav-volume/main.go) - change the volume of a WAV file 15 | 16 | # Documentation 17 | 18 | Full documentation is [here](http://godoc.org/github.com/unixpickle/wav) 19 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrDone = errors.New("Done reading audio data.") 7 | ErrSampleSize = errors.New("Unsupported sample size.") 8 | ErrInvalid = errors.New("The input data was invalid.") 9 | ) 10 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "time" 7 | ) 8 | 9 | // ChunkHeader is the generic 64-bit header in WAV files. 10 | type ChunkHeader struct { 11 | ID uint32 12 | Size uint32 13 | } 14 | 15 | // FileHeader is the "RIFF" chunk 16 | type FileHeader struct { 17 | ChunkHeader 18 | Format uint32 19 | } 20 | 21 | // Valid returns true only if the ID and format match the expected values for 22 | // WAVE files. 23 | func (h FileHeader) Valid() bool { 24 | return h.ID == 0x46464952 && h.Format == 0x45564157 25 | } 26 | 27 | // FormatHeader is the "fmt" sub-chunk 28 | type FormatHeader struct { 29 | ChunkHeader 30 | AudioFormat uint16 31 | NumChannels uint16 32 | SampleRate uint32 33 | ByteRate uint32 34 | BlockAlign uint16 35 | BitsPerSample uint16 36 | } 37 | 38 | // BlockSize returns the number of bytes per sample-channel. 39 | func (f FormatHeader) BlockSize() uint16 { 40 | return (f.BitsPerSample / 8) * f.NumChannels 41 | } 42 | 43 | // Returns true only if the ID, size, and audio format match those of a PCM WAV 44 | // audio file. 45 | func (f FormatHeader) Valid() bool { 46 | return f.ID == 0x20746d66 && f.Size == 0x10 && f.AudioFormat == 1 47 | } 48 | 49 | // Header is the canonical header for all WAV files 50 | type Header struct { 51 | File FileHeader 52 | Format FormatHeader 53 | Data ChunkHeader 54 | } 55 | 56 | // NewHeader creates a header with some reasonable defaults. 57 | func NewHeader() *Header { 58 | var result Header 59 | result.File.ID = 0x46464952 60 | result.File.Format = 0x45564157 61 | result.Format.ID = 0x20746d66 62 | result.Format.Size = 0x10 63 | result.Format.AudioFormat = 1 64 | result.Data.ID = 0x61746164 65 | return &result 66 | } 67 | 68 | // ReadHeader reads a header from a reader. 69 | // This does basic verification to make sure the header is valid. 70 | func ReadHeader(r io.Reader) (*Header, error) { 71 | var h Header 72 | 73 | // Attempt to read the header 74 | err := binary.Read(r, binary.LittleEndian, &h) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | // Skip over arbitrary chunks that are not the data chunk 80 | for h.File.Valid() && h.Format.Valid() && h.Data.ID != 0x61746164 { 81 | unused := int(h.Data.Size) 82 | _, err := io.ReadFull(r, make([]byte, unused)) 83 | if err != nil { 84 | return nil, err 85 | } 86 | err = binary.Read(r, binary.LittleEndian, &h.Data) 87 | if err != nil { 88 | return nil, err 89 | } 90 | } 91 | 92 | // Make sure the header is valid 93 | if !h.Valid() { 94 | return nil, ErrInvalid 95 | } 96 | 97 | // Make sure we support the bitrate 98 | sSize := h.Format.BitsPerSample 99 | if sSize != 8 && sSize != 16 { 100 | return nil, ErrSampleSize 101 | } 102 | 103 | return &h, nil 104 | } 105 | 106 | // Duration returns the duration of the WAV file. 107 | func (h *Header) Duration() time.Duration { 108 | samples := h.Data.Size / uint32(h.Format.BlockSize()) 109 | seconds := float64(samples) / float64(h.Format.SampleRate) 110 | return time.Duration(seconds * float64(time.Second)) 111 | } 112 | 113 | // Valid returns true only if the header is for a valid WAV PCM audio file. 114 | func (h *Header) Valid() bool { 115 | return h.File.Valid() && h.Format.Valid() && h.Data.ID == 0x61746164 116 | } 117 | 118 | // Write writes the header to a writer. 119 | func (h *Header) Write(w io.Writer) error { 120 | return binary.Write(w, binary.LittleEndian, h) 121 | } 122 | 123 | type fmtContent struct { 124 | AudioFormat uint16 125 | NumChannels uint16 126 | SampleRate uint32 127 | ByteRate uint32 128 | BlockAlign uint16 129 | BitsPerSample uint16 130 | } 131 | -------------------------------------------------------------------------------- /manip.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import "time" 4 | 5 | // Append appends sounds to a sound. 6 | // This method adds and removes channels and modifies sample rates as needed. 7 | func Append(dest Sound, sounds ...Sound) { 8 | for _, source := range sounds { 9 | if dest.SampleRate() == source.SampleRate() && 10 | dest.Channels() == source.Channels() { 11 | // This is the simple, fast case 12 | dest.SetSamples(append(dest.Samples(), source.Samples()...)) 13 | continue 14 | } 15 | // Generic conversion algorithm. 16 | ratio := float64(dest.SampleRate()) / float64(source.SampleRate()) 17 | sourceBlocks := len(source.Samples()) / source.Channels() 18 | destBlocks := int(float64(sourceBlocks) * ratio) 19 | mutualChannels := source.Channels() 20 | if dest.Channels() < mutualChannels { 21 | mutualChannels = dest.Channels() 22 | } 23 | for i := 0; i < destBlocks; i++ { 24 | sourceIdx := source.Channels() * int(float64(i)/ratio) 25 | newSamples := source.Samples()[sourceIdx : sourceIdx+mutualChannels] 26 | dest.SetSamples(append(dest.Samples(), newSamples...)) 27 | // Duplicate the first source channel for the remaining channels in 28 | // the destination. 29 | for i := mutualChannels; i < dest.Channels(); i++ { 30 | dest.SetSamples(append(dest.Samples(), newSamples[0])) 31 | } 32 | } 33 | } 34 | } 35 | 36 | // AppendSilence appends a certain amount of silence to a Sound. 37 | func AppendSilence(s Sound, t time.Duration) { 38 | count := unclippedSampleIndex(s, t) 39 | list := make([]Sample, count) 40 | s.SetSamples(append(s.Samples(), list...)) 41 | } 42 | 43 | // Crop isolates a time segment in a sound. 44 | // If the end time is past the sound's duration, it will 45 | // be clamped to the end of the sound. 46 | func Crop(s Sound, start, end time.Duration) { 47 | if len(s.Samples()) == 0 { 48 | return 49 | } 50 | 51 | startIdx := sampleIndex(s, start) 52 | endIdx := sampleIndex(s, end) 53 | 54 | if endIdx < startIdx { 55 | startIdx, endIdx = endIdx, startIdx 56 | } 57 | 58 | samps := make([]Sample, endIdx-startIdx) 59 | copy(samps, s.Samples()[startIdx:endIdx]) 60 | s.SetSamples(samps) 61 | } 62 | 63 | // Gradient creates a linear fade-in gradient for an audio file. 64 | // The voume is 0% at start and 100% at end. 65 | func Gradient(s Sound, start, end time.Duration) { 66 | if len(s.Samples()) == 0 { 67 | return 68 | } 69 | startIdx := sampleIndex(s, start) 70 | endIdx := sampleIndex(s, end) 71 | upwards := (startIdx < endIdx) 72 | if !upwards { 73 | startIdx, endIdx = endIdx, startIdx 74 | } 75 | for i := startIdx; i < endIdx; i += s.Channels() { 76 | value := Sample(i-startIdx) / Sample(endIdx-startIdx) 77 | if !upwards { 78 | value = 1.0 - value 79 | } 80 | for j := 0; j < s.Channels(); j++ { 81 | s.Samples()[i+j] *= value 82 | } 83 | } 84 | } 85 | 86 | // Overlay overlays a sound over another sound at a certain offset. 87 | // The overlaying sound will be converted to match the destination's sample 88 | // rate and channel count. 89 | func Overlay(s, o Sound, delay time.Duration) { 90 | // Convert the overlay if needed. 91 | if o.Channels() != s.Channels() || o.SampleRate() != s.SampleRate() { 92 | dest := NewPCM16Sound(s.Channels(), s.SampleRate()) 93 | Append(dest, o) 94 | Overlay(s, dest, delay) 95 | return 96 | } 97 | 98 | // Figure out the length of the new sound. 99 | start := unclippedSampleIndex(s, delay) 100 | sSize := len(s.Samples()) 101 | oSize := len(o.Samples()) 102 | totalSize := sSize 103 | if start+oSize > totalSize { 104 | totalSize = start + oSize 105 | } 106 | 107 | // Perform the actual overlay 108 | for i := 0; i < totalSize; i++ { 109 | if i >= sSize { 110 | s.SetSamples(append(s.Samples(), 0)) 111 | } 112 | if i >= start && i < start+oSize { 113 | sample := o.Samples()[i-start] 114 | s.Samples()[i] = clamp(s.Samples()[i] + sample) 115 | } 116 | } 117 | } 118 | 119 | // Volume scales all the samples in a Sound. 120 | func Volume(s Sound, scale float64) { 121 | sScale := Sample(scale) 122 | for i, sample := range s.Samples() { 123 | s.Samples()[i] = clamp(sample * sScale) 124 | } 125 | } 126 | 127 | func clamp(s Sample) Sample { 128 | if s < -1.0 { 129 | return -1 130 | } else if s > 1.0 { 131 | return 1 132 | } 133 | return s 134 | } 135 | 136 | func sampleIndex(s Sound, t time.Duration) int { 137 | index := unclippedSampleIndex(s, t) 138 | if index > len(s.Samples()) { 139 | return len(s.Samples()) 140 | } 141 | return index 142 | } 143 | 144 | func unclippedSampleIndex(s Sound, t time.Duration) int { 145 | secs := float64(t) / float64(time.Second) 146 | index := int(secs*float64(s.SampleRate())) * s.Channels() 147 | if index < 0 { 148 | return 0 149 | } 150 | return index 151 | } 152 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import "io" 4 | 5 | type Sample float64 6 | 7 | type Reader interface { 8 | // Header returns the reader's WAV header. 9 | Header() *Header 10 | 11 | // Read reads as many as len(out) samples. 12 | // The samples are signed values ranging between -1.0 and 1.0. 13 | // Channels are packed side-by-side, so for a stereo track it'd be LRLRLR... 14 | // If the end of stream is reached, ErrDone will be returned. 15 | Read(out []Sample) (int, error) 16 | 17 | // Remaining returns the number of samples left to read. 18 | Remaining() int 19 | } 20 | 21 | // NewReader wraps an io.Reader with a Reader. 22 | func NewReader(r io.Reader) (Reader, error) { 23 | h, err := ReadHeader(r) 24 | if err != nil { 25 | return nil, err 26 | } 27 | remaining := int(h.Data.Size / uint32(h.Format.BitsPerSample/8)) 28 | if h.Format.BitsPerSample == 8 { 29 | return &pcm8Reader{reader{h, r, remaining}}, nil 30 | } else if h.Format.BitsPerSample == 16 { 31 | return &pcm16Reader{reader{h, r, remaining}}, nil 32 | } 33 | return nil, ErrSampleSize 34 | } 35 | 36 | type pcm8Reader struct { 37 | reader 38 | } 39 | 40 | func (r pcm8Reader) Read(out []Sample) (int, error) { 41 | if r.remaining == 0 { 42 | return 0, ErrDone 43 | } 44 | 45 | toRead := len(out) 46 | if toRead > r.remaining { 47 | toRead = r.remaining 48 | } 49 | 50 | // Decode the list of raw samples 51 | raw := make([]byte, toRead) 52 | if _, err := io.ReadFull(r.input, raw); err != nil { 53 | return 0, err 54 | } 55 | for i, x := range raw { 56 | out[i] = (Sample(x) - 0x80) / 0x80 57 | } 58 | 59 | // Return the amount read and a possible ErrDone error. 60 | r.remaining -= toRead 61 | if r.remaining == 0 { 62 | return toRead, ErrDone 63 | } 64 | return toRead, nil 65 | } 66 | 67 | type pcm16Reader struct { 68 | reader 69 | } 70 | 71 | func (r pcm16Reader) Read(out []Sample) (int, error) { 72 | if r.remaining == 0 { 73 | return 0, ErrDone 74 | } 75 | 76 | toRead := len(out) 77 | if toRead > r.remaining { 78 | toRead = r.remaining 79 | } 80 | 81 | // Decode the list of raw samples 82 | raw := make([]byte, toRead*2) 83 | if _, err := io.ReadFull(r.input, raw); err != nil { 84 | return 0, err 85 | } 86 | for i := 0; i < len(raw); i += 2 { 87 | sample := int16(uint16(raw[i]) | (uint16(raw[i+1]) << 8)) 88 | out[i>>1] = Sample(sample) / 0x8000 89 | } 90 | 91 | // Return the amount read and a possible ErrDone error. 92 | r.remaining -= toRead 93 | if r.remaining == 0 { 94 | return toRead, ErrDone 95 | } 96 | return toRead, nil 97 | } 98 | 99 | type reader struct { 100 | header *Header 101 | input io.Reader 102 | remaining int 103 | } 104 | 105 | func (r *reader) Header() *Header { 106 | return r.header 107 | } 108 | 109 | func (r *reader) Remaining() int { 110 | return r.remaining 111 | } 112 | -------------------------------------------------------------------------------- /reader_test.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkReader(b *testing.B) { 10 | sound := NewPCM16Sound(2, 22050) 11 | sound.SetSamples(make([]Sample, 22050*2*10)) 12 | var buf bytes.Buffer 13 | if err := sound.Write(&buf); err != nil { 14 | b.Fatal(err) 15 | } 16 | 17 | reader := bytes.NewReader(buf.Bytes()) 18 | 19 | b.ResetTimer() 20 | for i := 0; i < b.N; i++ { 21 | reader.Seek(0, io.SeekStart) 22 | ReadSound(reader) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sound.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "os" 7 | "time" 8 | ) 9 | 10 | // Sound represents and abstract list of samples which can be encoded to a 11 | // file. 12 | type Sound interface { 13 | Channels() int 14 | Clone() Sound 15 | Duration() time.Duration 16 | SampleRate() int 17 | Samples() []Sample 18 | SetSamples([]Sample) 19 | Write(io.Writer) error 20 | } 21 | 22 | // WriteFile saves a sound to a file. 23 | func WriteFile(s Sound, path string) error { 24 | f, err := os.Create(path) 25 | if err != nil { 26 | return err 27 | } 28 | defer f.Close() 29 | return s.Write(f) 30 | } 31 | 32 | // NewPCM8Sound creates a new empty Sound with given parameters. 33 | func NewPCM8Sound(channels int, sampleRate int) Sound { 34 | res := wavSound8{wavSound{NewHeader(), []Sample{}}} 35 | res.header.Format.BitsPerSample = 8 36 | res.header.Format.BlockAlign = uint16(channels) 37 | res.header.Format.ByteRate = uint32(sampleRate * channels) 38 | res.header.Format.SampleRate = uint32(sampleRate) 39 | res.header.Format.NumChannels = uint16(channels) 40 | return &res 41 | } 42 | 43 | // NewPCM16Sound creates a new empty Sound with given parameters. 44 | func NewPCM16Sound(channels int, sampleRate int) Sound { 45 | res := wavSound16{wavSound{NewHeader(), []Sample{}}} 46 | res.header.Format.BitsPerSample = 16 47 | res.header.Format.BlockAlign = uint16(channels * 2) 48 | res.header.Format.ByteRate = uint32(sampleRate * channels * 2) 49 | res.header.Format.SampleRate = uint32(sampleRate) 50 | res.header.Format.NumChannels = uint16(channels) 51 | return &res 52 | } 53 | 54 | // ReadSound reads a sound from an io.Reader. 55 | func ReadSound(f io.Reader) (Sound, error) { 56 | r, err := NewReader(f) 57 | if err != nil { 58 | return nil, err 59 | } 60 | if r.Header().Format.BitsPerSample != 8 && 61 | r.Header().Format.BitsPerSample != 16 { 62 | return nil, ErrSampleSize 63 | } 64 | samples := make([]Sample, r.Remaining()) 65 | _, err = r.Read(samples) 66 | if err != ErrDone && err != nil { 67 | return nil, err 68 | } 69 | if r.Header().Format.BitsPerSample == 8 { 70 | return &wavSound8{wavSound{r.Header(), samples}}, nil 71 | } else { 72 | return &wavSound16{wavSound{r.Header(), samples}}, nil 73 | } 74 | } 75 | 76 | func ReadSoundFile(path string) (Sound, error) { 77 | f, err := os.Open(path) 78 | if err != nil { 79 | return nil, err 80 | } 81 | defer f.Close() 82 | return ReadSound(f) 83 | } 84 | 85 | type wavSound struct { 86 | header *Header 87 | samples []Sample 88 | } 89 | 90 | func (s *wavSound) Channels() int { 91 | return int(s.header.Format.NumChannels) 92 | } 93 | 94 | func (s *wavSound) Clone() wavSound { 95 | newSamples := make([]Sample, len(s.samples)) 96 | copy(newSamples, s.samples) 97 | return wavSound{s.header, newSamples} 98 | } 99 | 100 | func (s *wavSound) Duration() time.Duration { 101 | return s.Header().Duration() 102 | } 103 | 104 | func (s *wavSound) Header() *Header { 105 | h := s.header 106 | h.Data.Size = uint32(s.header.Format.BitsPerSample/8) * 107 | uint32(len(s.Samples())) 108 | h.File.Size = 36 + s.header.Data.Size 109 | return h 110 | } 111 | 112 | func (s *wavSound) SampleRate() int { 113 | return int(s.header.Format.SampleRate) 114 | } 115 | 116 | func (s *wavSound) Samples() []Sample { 117 | return s.samples 118 | } 119 | 120 | func (s *wavSound) SetSamples(ss []Sample) { 121 | s.samples = ss 122 | } 123 | 124 | type wavSound8 struct { 125 | wavSound 126 | } 127 | 128 | func (s *wavSound8) Clone() Sound { 129 | return &wavSound8{s.wavSound.Clone()} 130 | } 131 | 132 | func (s *wavSound8) Write(w io.Writer) error { 133 | // Write the header 134 | if err := binary.Write(w, binary.LittleEndian, s.Header()); err != nil { 135 | return err 136 | } 137 | data := make([]byte, len(s.Samples())) 138 | // Write the actual data 139 | for i, sample := range s.Samples() { 140 | data[i] = byte(sample*0x80 + 0x80) 141 | } 142 | if _, err := w.Write(data); err != nil { 143 | return err 144 | } 145 | return nil 146 | } 147 | 148 | type wavSound16 struct { 149 | wavSound 150 | } 151 | 152 | func (s *wavSound16) Clone() Sound { 153 | return &wavSound16{s.wavSound.Clone()} 154 | } 155 | 156 | func (s *wavSound16) Write(w io.Writer) error { 157 | // Write the header 158 | if err := binary.Write(w, binary.LittleEndian, s.Header()); err != nil { 159 | return err 160 | } 161 | data := make([]int16, len(s.Samples())) 162 | for i, sample := range s.Samples() { 163 | data[i] = int16(sample * 0x8000) 164 | } 165 | if err := binary.Write(w, binary.LittleEndian, data); err != nil { 166 | return err 167 | } 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /wav-crop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/unixpickle/wav" 7 | "os" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | if err := ErrMain(); err != nil { 14 | fmt.Fprintln(os.Stderr, err) 15 | os.Exit(1) 16 | } 17 | } 18 | 19 | func ErrMain() error { 20 | if len(os.Args) != 5 { 21 | return errors.New("Usage: wav-crop " + 22 | "") 23 | } 24 | s, err := wav.ReadSoundFile(os.Args[1]) 25 | if err != nil { 26 | return err 27 | } 28 | start, err := strconv.ParseFloat(os.Args[2], 64) 29 | if err != nil { 30 | return err 31 | } 32 | end, err := strconv.ParseFloat(os.Args[3], 64) 33 | if err != nil { 34 | return err 35 | } 36 | wav.Crop(s, time.Duration(start*float64(time.Second)), 37 | time.Duration(end*float64(time.Second))) 38 | return wav.WriteFile(s, os.Args[4]) 39 | } 40 | -------------------------------------------------------------------------------- /wav-duration/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/unixpickle/wav" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | if err := ErrMain(); err != nil { 12 | fmt.Fprintln(os.Stderr, err) 13 | os.Exit(1) 14 | } 15 | } 16 | 17 | func ErrMain() error { 18 | if len(os.Args) != 2 { 19 | return errors.New("Usage: wav-duration ") 20 | } 21 | f, err := os.Open(os.Args[1]) 22 | if err != nil { 23 | return err 24 | } 25 | header, err := wav.ReadHeader(f) 26 | f.Close() 27 | if err != nil { 28 | return err 29 | } 30 | fmt.Println("duration is", header.Duration()) 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /wav-gradient/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/unixpickle/wav" 7 | "os" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | if err := ErrMain(); err != nil { 14 | fmt.Fprintln(os.Stderr, err) 15 | os.Exit(1) 16 | } 17 | } 18 | 19 | func ErrMain() error { 20 | if len(os.Args) != 5 { 21 | return errors.New("Usage: wav-gradient " + 22 | "") 23 | } 24 | s, err := wav.ReadSoundFile(os.Args[1]) 25 | if err != nil { 26 | return err 27 | } 28 | start, err := strconv.ParseFloat(os.Args[2], 64) 29 | if err != nil { 30 | return err 31 | } 32 | end, err := strconv.ParseFloat(os.Args[3], 64) 33 | if err != nil { 34 | return err 35 | } 36 | wav.Gradient(s, time.Duration(start*float64(time.Second)), 37 | time.Duration(end*float64(time.Second))) 38 | return wav.WriteFile(s, os.Args[4]) 39 | } 40 | -------------------------------------------------------------------------------- /wav-join/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/unixpickle/wav" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | if err := ErrMain(); err != nil { 12 | fmt.Fprintln(os.Stderr, err) 13 | os.Exit(1) 14 | } 15 | } 16 | 17 | func ErrMain() error { 18 | if len(os.Args) < 3 { 19 | return errors.New("Usage: wav-join [ ...] " + 20 | "") 21 | } 22 | s, err := wav.ReadSoundFile(os.Args[1]) 23 | if err != nil { 24 | return err 25 | } 26 | for _, f := range os.Args[2 : len(os.Args)-1] { 27 | nextS, err := wav.ReadSoundFile(f) 28 | if err != nil { 29 | return err 30 | } 31 | wav.Append(s, nextS) 32 | } 33 | return wav.WriteFile(s, os.Args[len(os.Args)-1]) 34 | } 35 | -------------------------------------------------------------------------------- /wav-overlay/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/unixpickle/wav" 7 | "os" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | if err := ErrMain(); err != nil { 14 | fmt.Fprintln(os.Stderr, err) 15 | os.Exit(1) 16 | } 17 | } 18 | 19 | func ErrMain() error { 20 | if len(os.Args) != 5 { 21 | return errors.New("Usage: wav-overlay " + 22 | " ") 23 | } 24 | start, err := strconv.ParseFloat(os.Args[3], 64) 25 | if err != nil { 26 | return err 27 | } 28 | s1, err := wav.ReadSoundFile(os.Args[1]) 29 | if err != nil { 30 | return err 31 | } 32 | s2, err := wav.ReadSoundFile(os.Args[2]) 33 | if err != nil { 34 | return err 35 | } 36 | wav.Volume(s1, 0.5) 37 | wav.Volume(s2, 0.5) 38 | wav.Overlay(s1, s2, time.Duration(start*float64(time.Second))) 39 | return wav.WriteFile(s1, os.Args[4]) 40 | } 41 | -------------------------------------------------------------------------------- /wav-tone/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/unixpickle/wav" 7 | "math" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | func main() { 13 | if err := ErrMain(); err != nil { 14 | fmt.Fprintln(os.Stderr, err) 15 | os.Exit(1) 16 | } 17 | } 18 | 19 | func ErrMain() error { 20 | if len(os.Args) != 3 { 21 | return errors.New("Usage: wav-tone ") 22 | } 23 | freq, err := strconv.Atoi(os.Args[1]) 24 | if err != nil { 25 | return err 26 | } 27 | sampleRate := 44100 28 | sound := wav.NewPCM8Sound(1, sampleRate) 29 | for i := 0; i < sampleRate*1; i++ { 30 | time := float64(i) / float64(sampleRate) 31 | value := wav.Sample(math.Sin(time * math.Pi * 2 * float64(freq))) 32 | sound.SetSamples(append(sound.Samples(), value)) 33 | } 34 | return wav.WriteFile(sound, os.Args[2]) 35 | } 36 | -------------------------------------------------------------------------------- /wav-volume/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/unixpickle/wav" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | func main() { 12 | if err := ErrMain(); err != nil { 13 | fmt.Fprintln(os.Stderr, err) 14 | os.Exit(1) 15 | } 16 | } 17 | 18 | func ErrMain() error { 19 | if len(os.Args) != 4 { 20 | return errors.New("Usage: wav-volume ") 21 | } 22 | s, err := wav.ReadSoundFile(os.Args[1]) 23 | if err != nil { 24 | return err 25 | } 26 | scale, err := strconv.ParseFloat(os.Args[2], 64) 27 | if err != nil { 28 | return err 29 | } 30 | wav.Volume(s, scale) 31 | return wav.WriteFile(s, os.Args[3]) 32 | } 33 | --------------------------------------------------------------------------------