├── .gitignore ├── LICENSE ├── README.md ├── dsputils ├── compare.go ├── dsputils.go ├── dsputils_test.go ├── matrix.go └── matrix_test.go ├── fft ├── bluestein.go ├── fft.go ├── fft_test.go └── radix2.go ├── spectral ├── pwelch.go ├── pwelch_test.go ├── spectral.go └── spectral_test.go ├── wav ├── float.wav ├── small.wav ├── wav.go └── wav_test.go └── window ├── window.go └── window_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | *.8 3 | *.out 4 | *.swp 5 | *~ 6 | _* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Matt Jibson 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GO-DSP 2 | 3 | go-dsp is a digital signal processing package for the [Go programming language](http://golang.org). 4 | 5 | ## Packages 6 | 7 | * **[dsputils](http://godoc.org/github.com/mjibson/go-dsp/dsputils)** - utilities and data structures for DSP 8 | * **[fft](http://godoc.org/github.com/mjibson/go-dsp/fft)** - fast Fourier transform 9 | * **[spectral](http://godoc.org/github.com/mjibson/go-dsp/spectral)** - power spectral density functions (e.g., Pwelch) 10 | * **[wav](http://godoc.org/github.com/mjibson/go-dsp/wav)** - wav file reader functions 11 | * **[window](http://godoc.org/github.com/mjibson/go-dsp/window)** - window functions (e.g., Hamming, Hann, Bartlett) 12 | 13 | ## Installation and Usage 14 | 15 | ```$ go get github.com/mjibson/go-dsp/fft``` 16 | 17 | ``` 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | 23 | "github.com/mjibson/go-dsp/fft" 24 | ) 25 | 26 | func main() { 27 | fmt.Println(fft.FFTReal([]float64 {1, 2, 3})) 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /dsputils/compare.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package dsputils 18 | 19 | import ( 20 | "math" 21 | ) 22 | 23 | const ( 24 | closeFactor = 1e-8 25 | ) 26 | 27 | // PrettyClose returns true if the slices a and b are very close, else false. 28 | func PrettyClose(a, b []float64) bool { 29 | if len(a) != len(b) { 30 | return false 31 | } 32 | 33 | for i, c := range a { 34 | if !Float64Equal(c, b[i]) { 35 | return false 36 | } 37 | } 38 | return true 39 | } 40 | 41 | // PrettyCloseC returns true if the slices a and b are very close, else false. 42 | func PrettyCloseC(a, b []complex128) bool { 43 | if len(a) != len(b) { 44 | return false 45 | } 46 | 47 | for i, c := range a { 48 | if !ComplexEqual(c, b[i]) { 49 | return false 50 | } 51 | } 52 | return true 53 | } 54 | 55 | // PrettyClose2 returns true if the matrixes a and b are very close, else false. 56 | func PrettyClose2(a, b [][]complex128) bool { 57 | if len(a) != len(b) { 58 | return false 59 | } 60 | 61 | for i, c := range a { 62 | if !PrettyCloseC(c, b[i]) { 63 | return false 64 | } 65 | } 66 | return true 67 | } 68 | 69 | // PrettyClose2F returns true if the matrixes a and b are very close, else false. 70 | func PrettyClose2F(a, b [][]float64) bool { 71 | if len(a) != len(b) { 72 | return false 73 | } 74 | 75 | for i, c := range a { 76 | if !PrettyClose(c, b[i]) { 77 | return false 78 | } 79 | } 80 | return true 81 | } 82 | 83 | // ComplexEqual returns true if a and b are very close, else false. 84 | func ComplexEqual(a, b complex128) bool { 85 | r_a := real(a) 86 | r_b := real(b) 87 | i_a := imag(a) 88 | i_b := imag(b) 89 | 90 | return Float64Equal(r_a, r_b) && Float64Equal(i_a, i_b) 91 | } 92 | 93 | // Float64Equal returns true if a and b are very close, else false. 94 | func Float64Equal(a, b float64) bool { 95 | return math.Abs(a-b) <= closeFactor || math.Abs(1-a/b) <= closeFactor 96 | } 97 | -------------------------------------------------------------------------------- /dsputils/dsputils.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | // Package dsputils provides functions useful in digital signal processing. 18 | package dsputils 19 | 20 | import ( 21 | "math" 22 | ) 23 | 24 | // ToComplex returns the complex equivalent of the real-valued slice. 25 | func ToComplex(x []float64) []complex128 { 26 | y := make([]complex128, len(x)) 27 | for n, v := range x { 28 | y[n] = complex(v, 0) 29 | } 30 | return y 31 | } 32 | 33 | // IsPowerOf2 returns true if x is a power of 2, else false. 34 | func IsPowerOf2(x int) bool { 35 | return x&(x-1) == 0 36 | } 37 | 38 | // NextPowerOf2 returns the next power of 2 >= x. 39 | func NextPowerOf2(x int) int { 40 | if IsPowerOf2(x) { 41 | return x 42 | } 43 | 44 | return int(math.Pow(2, math.Ceil(math.Log2(float64(x))))) 45 | } 46 | 47 | // ZeroPad returns x with zeros appended to the end to the specified length. 48 | // If len(x) >= length, x is returned, otherwise a new array is returned. 49 | func ZeroPad(x []complex128, length int) []complex128 { 50 | if len(x) >= length { 51 | return x 52 | } 53 | 54 | r := make([]complex128, length) 55 | copy(r, x) 56 | return r 57 | } 58 | 59 | // ZeroPadF returns x with zeros appended to the end to the specified length. 60 | // If len(x) >= length, x is returned, otherwise a new array is returned. 61 | func ZeroPadF(x []float64, length int) []float64 { 62 | if len(x) >= length { 63 | return x 64 | } 65 | 66 | r := make([]float64, length) 67 | copy(r, x) 68 | return r 69 | } 70 | 71 | // ZeroPad2 returns ZeroPad of x, with the length as the next power of 2 >= len(x). 72 | func ZeroPad2(x []complex128) []complex128 { 73 | return ZeroPad(x, NextPowerOf2(len(x))) 74 | } 75 | 76 | // ToComplex2 returns the complex equivalent of the real-valued matrix. 77 | func ToComplex2(x [][]float64) [][]complex128 { 78 | y := make([][]complex128, len(x)) 79 | for n, v := range x { 80 | y[n] = ToComplex(v) 81 | } 82 | return y 83 | } 84 | 85 | // Segment returns segs equal-length slices that are segments of x with noverlap% of overlap. 86 | // The returned slices are not copies of x, but slices into it. 87 | // Trailing entries in x that connot be included in the equal-length segments are discarded. 88 | // noverlap is a percentage, thus 0 <= noverlap <= 1, and noverlap = 0.5 is 50% overlap. 89 | func Segment(x []complex128, segs int, noverlap float64) [][]complex128 { 90 | lx := len(x) 91 | 92 | // determine step, length, and overlap 93 | var overlap, length, step, tot int 94 | for length = lx; length > 0; length-- { 95 | overlap = int(float64(length) * noverlap) 96 | tot = segs*(length-overlap) + overlap 97 | if tot <= lx { 98 | step = length - overlap 99 | break 100 | } 101 | } 102 | 103 | if length == 0 { 104 | panic("too many segments") 105 | } 106 | 107 | r := make([][]complex128, segs) 108 | s := 0 109 | for n := range r { 110 | r[n] = x[s : s+length] 111 | s += step 112 | } 113 | 114 | return r 115 | } 116 | -------------------------------------------------------------------------------- /dsputils/dsputils_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package dsputils 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | type segmentTest struct { 24 | segs int 25 | noverlap float64 26 | slices [][]int 27 | } 28 | 29 | var segmentTests = []segmentTest{ 30 | { 31 | 3, 32 | .5, 33 | [][]int{ 34 | {0, 8}, 35 | {4, 12}, 36 | {8, 16}, 37 | }, 38 | }, 39 | } 40 | 41 | func TestSegment(t *testing.T) { 42 | x := make([]complex128, 16) 43 | for n := range x { 44 | x[n] = complex(float64(n), 0) 45 | } 46 | 47 | for _, st := range segmentTests { 48 | v := Segment(x, st.segs, st.noverlap) 49 | s := make([][]complex128, st.segs) 50 | for i, sl := range st.slices { 51 | s[i] = x[sl[0]:sl[1]] 52 | } 53 | 54 | if !PrettyClose2(v, s) { 55 | t.Error("Segment error: expected:", s, ", output:", v) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /dsputils/matrix.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package dsputils 18 | 19 | // Matrix is a multidimensional matrix of arbitrary size and dimension. 20 | // It cannot be resized after creation. Arrays in any axis can be set or fetched. 21 | type Matrix struct { 22 | list []complex128 23 | dims, offsets []int 24 | } 25 | 26 | // MakeMatrix returns a new Matrix populated with x having dimensions dims. 27 | // For example, to create a 3-dimensional Matrix with 2 components, 3 rows, and 4 columns: 28 | // MakeMatrix([]complex128 { 29 | // 1, 2, 3, 4, 30 | // 5, 6, 7, 8, 31 | // 9, 0, 1, 2, 32 | // 33 | // 3, 4, 5, 6, 34 | // 7, 8, 9, 0, 35 | // 4, 3, 2, 1}, 36 | // []int {2, 3, 4}) 37 | func MakeMatrix(x []complex128, dims []int) *Matrix { 38 | length := 1 39 | offsets := make([]int, len(dims)) 40 | 41 | for i := len(dims) - 1; i >= 0; i-- { 42 | if dims[i] < 1 { 43 | panic("invalid dimensions") 44 | } 45 | 46 | offsets[i] = length 47 | length *= dims[i] 48 | } 49 | 50 | if len(x) != length { 51 | panic("incorrect dimensions") 52 | } 53 | 54 | dc := make([]int, len(dims)) 55 | copy(dc, dims) 56 | return &Matrix{x, dc, offsets} 57 | } 58 | 59 | // MakeMatrix2 is a helper function to convert a 2-d array to a matrix. 60 | func MakeMatrix2(x [][]complex128) *Matrix { 61 | dims := []int{len(x), len(x[0])} 62 | r := make([]complex128, dims[0]*dims[1]) 63 | for n, v := range x { 64 | if len(v) != dims[1] { 65 | panic("ragged array") 66 | } 67 | 68 | copy(r[n*dims[1]:(n+1)*dims[1]], v) 69 | } 70 | 71 | return MakeMatrix(r, dims) 72 | } 73 | 74 | // Copy returns a new copy of m. 75 | func (m *Matrix) Copy() *Matrix { 76 | r := &Matrix{m.list, m.dims, m.offsets} 77 | r.list = make([]complex128, len(m.list)) 78 | copy(r.list, m.list) 79 | return r 80 | } 81 | 82 | // MakeEmptyMatrix creates an empty Matrix with given dimensions. 83 | func MakeEmptyMatrix(dims []int) *Matrix { 84 | x := 1 85 | for _, v := range dims { 86 | x *= v 87 | } 88 | 89 | return MakeMatrix(make([]complex128, x), dims) 90 | } 91 | 92 | // offset returns the index in the one-dimensional array 93 | func (s *Matrix) offset(dims []int) int { 94 | if len(dims) != len(s.dims) { 95 | panic("incorrect dimensions") 96 | } 97 | 98 | i := 0 99 | for n, v := range dims { 100 | if v > s.dims[n] { 101 | panic("incorrect dimensions") 102 | } 103 | 104 | i += v * s.offsets[n] 105 | } 106 | 107 | return i 108 | } 109 | 110 | func (m *Matrix) indexes(dims []int) []int { 111 | i := -1 112 | for n, v := range dims { 113 | if v == -1 { 114 | if i >= 0 { 115 | panic("only one dimension index allowed") 116 | } 117 | 118 | i = n 119 | } else if v >= m.dims[n] { 120 | panic("dimension out of bounds") 121 | } 122 | } 123 | 124 | if i == -1 { 125 | panic("must specify one dimension index") 126 | } 127 | 128 | x := 0 129 | for n, v := range dims { 130 | if v >= 0 { 131 | x += m.offsets[n] * v 132 | } 133 | } 134 | 135 | r := make([]int, m.dims[i]) 136 | for j := range r { 137 | r[j] = x + m.offsets[i]*j 138 | } 139 | 140 | return r 141 | } 142 | 143 | // Dimensions returns the dimension array of the Matrix. 144 | func (m *Matrix) Dimensions() []int { 145 | r := make([]int, len(m.dims)) 146 | copy(r, m.dims) 147 | return r 148 | } 149 | 150 | // Dim returns the array of any given index of the Matrix. 151 | // Exactly one value in dims must be -1. This is the array dimension returned. 152 | // For example, using the Matrix documented in MakeMatrix: 153 | // m.Dim([]int {1, 0, -1}) = []complex128 {3, 4, 5, 6} 154 | // m.Dim([]int {0, -1, 2}) = []complex128 {3, 7, 1} 155 | // m.Dim([]int {-1, 1, 3}) = []complex128 {8, 0} 156 | func (s *Matrix) Dim(dims []int) []complex128 { 157 | inds := s.indexes(dims) 158 | r := make([]complex128, len(inds)) 159 | for n, v := range inds { 160 | r[n] = s.list[v] 161 | } 162 | 163 | return r 164 | } 165 | 166 | func (m *Matrix) SetDim(x []complex128, dims []int) { 167 | inds := m.indexes(dims) 168 | if len(x) != len(inds) { 169 | panic("incorrect array length") 170 | } 171 | 172 | for n, v := range inds { 173 | m.list[v] = x[n] 174 | } 175 | } 176 | 177 | // Value returns the value at the given index. 178 | // m.Value([]int {1, 2, 3, 4}) is equivalent to m[1][2][3][4]. 179 | func (s *Matrix) Value(dims []int) complex128 { 180 | return s.list[s.offset(dims)] 181 | } 182 | 183 | // SetValue sets the value at the given index. 184 | // m.SetValue(10, []int {1, 2, 3, 4}) is equivalent to m[1][2][3][4] = 10. 185 | func (s *Matrix) SetValue(x complex128, dims []int) { 186 | s.list[s.offset(dims)] = x 187 | } 188 | 189 | // To2D returns the 2-D array equivalent of the Matrix. 190 | // Only works on Matrixes of 2 dimensions. 191 | func (m *Matrix) To2D() [][]complex128 { 192 | if len(m.dims) != 2 { 193 | panic("can only convert 2-D Matrixes") 194 | } 195 | 196 | r := make([][]complex128, m.dims[0]) 197 | for i := 0; i < m.dims[0]; i++ { 198 | r[i] = make([]complex128, m.dims[1]) 199 | copy(r[i], m.list[i*m.dims[1]:(i+1)*m.dims[1]]) 200 | } 201 | 202 | return r 203 | } 204 | 205 | // PrettyClose returns true if the Matrixes are very close, else false. 206 | // Comparison done using dsputils.PrettyCloseC(). 207 | func (m *Matrix) PrettyClose(n *Matrix) bool { 208 | // todo: use new slice equality comparison 209 | for i, v := range m.dims { 210 | if v != n.dims[i] { 211 | return false 212 | } 213 | } 214 | 215 | return PrettyCloseC(m.list, n.list) 216 | } 217 | -------------------------------------------------------------------------------- /dsputils/matrix_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package dsputils 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestMakeMatrix(t *testing.T) { 24 | m := MakeMatrix( 25 | []complex128{ 26 | 1, 2, 3, 4, 27 | 5, 6, 7, 8, 28 | 9, 0, 1, 2, 29 | 30 | 3, 4, 5, 6, 31 | 7, 8, 9, 0, 32 | 4, 3, 2, 1}, 33 | []int{2, 3, 4}) 34 | 35 | checkArr(t, m.Dim([]int{1, 0, -1}), ToComplex([]float64{3, 4, 5, 6})) 36 | checkArr(t, m.Dim([]int{0, -1, 2}), ToComplex([]float64{3, 7, 1})) 37 | checkArr(t, m.Dim([]int{-1, 1, 3}), ToComplex([]float64{8, 0})) 38 | 39 | s := ToComplex([]float64{10, 11, 12}) 40 | i := []int{1, -1, 3} 41 | m.SetDim(s, i) 42 | checkArr(t, m.Dim(i), s) 43 | 44 | v := complex(14, 0) 45 | m.SetValue(v, i) 46 | checkFloat(t, m.Value(i), v) 47 | } 48 | 49 | func checkArr(t *testing.T, have, want []complex128) { 50 | if !PrettyCloseC(have, want) { 51 | t.Error("have:", have, "want:", want) 52 | } 53 | } 54 | 55 | func checkFloat(t *testing.T, have, want complex128) { 56 | if !ComplexEqual(have, want) { 57 | t.Error("have:", have, "want:", want) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /fft/bluestein.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package fft 18 | 19 | import ( 20 | "math" 21 | "sync" 22 | 23 | "github.com/mjibson/go-dsp/dsputils" 24 | ) 25 | 26 | var ( 27 | bluesteinLock sync.RWMutex 28 | bluesteinFactors = map[int][]complex128{} 29 | bluesteinInvFactors = map[int][]complex128{} 30 | ) 31 | 32 | func getBluesteinFactors(input_len int) ([]complex128, []complex128) { 33 | bluesteinLock.RLock() 34 | 35 | if hasBluesteinFactors(input_len) { 36 | defer bluesteinLock.RUnlock() 37 | return bluesteinFactors[input_len], bluesteinInvFactors[input_len] 38 | } 39 | 40 | bluesteinLock.RUnlock() 41 | bluesteinLock.Lock() 42 | defer bluesteinLock.Unlock() 43 | 44 | if !hasBluesteinFactors(input_len) { 45 | bluesteinFactors[input_len] = make([]complex128, input_len) 46 | bluesteinInvFactors[input_len] = make([]complex128, input_len) 47 | 48 | var sin, cos float64 49 | for i := 0; i < input_len; i++ { 50 | if i == 0 { 51 | sin, cos = 0, 1 52 | } else { 53 | sin, cos = math.Sincos(math.Pi / float64(input_len) * float64(i*i)) 54 | } 55 | bluesteinFactors[input_len][i] = complex(cos, sin) 56 | bluesteinInvFactors[input_len][i] = complex(cos, -sin) 57 | } 58 | } 59 | 60 | return bluesteinFactors[input_len], bluesteinInvFactors[input_len] 61 | } 62 | 63 | func hasBluesteinFactors(idx int) bool { 64 | return bluesteinFactors[idx] != nil 65 | } 66 | 67 | // bluesteinFFT returns the FFT calculated using the Bluestein algorithm. 68 | func bluesteinFFT(x []complex128) []complex128 { 69 | lx := len(x) 70 | a := dsputils.ZeroPad(x, dsputils.NextPowerOf2(lx*2-1)) 71 | la := len(a) 72 | factors, invFactors := getBluesteinFactors(lx) 73 | 74 | for n, v := range x { 75 | a[n] = v * invFactors[n] 76 | } 77 | 78 | b := make([]complex128, la) 79 | for i := 0; i < lx; i++ { 80 | b[i] = factors[i] 81 | 82 | if i != 0 { 83 | b[la-i] = factors[i] 84 | } 85 | } 86 | 87 | r := Convolve(a, b) 88 | 89 | for i := 0; i < lx; i++ { 90 | r[i] *= invFactors[i] 91 | } 92 | 93 | return r[:lx] 94 | } 95 | -------------------------------------------------------------------------------- /fft/fft.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | // Package fft provides forward and inverse fast Fourier transform functions. 18 | package fft 19 | 20 | import ( 21 | "github.com/mjibson/go-dsp/dsputils" 22 | ) 23 | 24 | // FFTReal returns the forward FFT of the real-valued slice. 25 | func FFTReal(x []float64) []complex128 { 26 | return FFT(dsputils.ToComplex(x)) 27 | } 28 | 29 | // IFFTReal returns the inverse FFT of the real-valued slice. 30 | func IFFTReal(x []float64) []complex128 { 31 | return IFFT(dsputils.ToComplex(x)) 32 | } 33 | 34 | // IFFT returns the inverse FFT of the complex-valued slice. 35 | func IFFT(x []complex128) []complex128 { 36 | lx := len(x) 37 | r := make([]complex128, lx) 38 | 39 | // Reverse inputs, which is calculated with modulo N, hence x[0] as an outlier 40 | r[0] = x[0] 41 | for i := 1; i < lx; i++ { 42 | r[i] = x[lx-i] 43 | } 44 | 45 | r = FFT(r) 46 | 47 | N := complex(float64(lx), 0) 48 | for n := range r { 49 | r[n] /= N 50 | } 51 | return r 52 | } 53 | 54 | // Convolve returns the convolution of x ∗ y. 55 | func Convolve(x, y []complex128) []complex128 { 56 | if len(x) != len(y) { 57 | panic("arrays not of equal size") 58 | } 59 | 60 | fft_x := FFT(x) 61 | fft_y := FFT(y) 62 | 63 | r := make([]complex128, len(x)) 64 | for i := 0; i < len(r); i++ { 65 | r[i] = fft_x[i] * fft_y[i] 66 | } 67 | 68 | return IFFT(r) 69 | } 70 | 71 | // FFT returns the forward FFT of the complex-valued slice. 72 | func FFT(x []complex128) []complex128 { 73 | lx := len(x) 74 | 75 | // todo: non-hack handling length <= 1 cases 76 | if lx <= 1 { 77 | r := make([]complex128, lx) 78 | copy(r, x) 79 | return r 80 | } 81 | 82 | if dsputils.IsPowerOf2(lx) { 83 | return radix2FFT(x) 84 | } 85 | 86 | return bluesteinFFT(x) 87 | } 88 | 89 | var ( 90 | worker_pool_size = 0 91 | ) 92 | 93 | // SetWorkerPoolSize sets the number of workers during FFT computation on multicore systems. 94 | // If n is 0 (the default), then GOMAXPROCS workers will be created. 95 | func SetWorkerPoolSize(n int) { 96 | if n < 0 { 97 | n = 0 98 | } 99 | 100 | worker_pool_size = n 101 | } 102 | 103 | // FFT2Real returns the 2-dimensional, forward FFT of the real-valued matrix. 104 | func FFT2Real(x [][]float64) [][]complex128 { 105 | return FFT2(dsputils.ToComplex2(x)) 106 | } 107 | 108 | // FFT2 returns the 2-dimensional, forward FFT of the complex-valued matrix. 109 | func FFT2(x [][]complex128) [][]complex128 { 110 | return computeFFT2(x, FFT) 111 | } 112 | 113 | // IFFT2Real returns the 2-dimensional, inverse FFT of the real-valued matrix. 114 | func IFFT2Real(x [][]float64) [][]complex128 { 115 | return IFFT2(dsputils.ToComplex2(x)) 116 | } 117 | 118 | // IFFT2 returns the 2-dimensional, inverse FFT of the complex-valued matrix. 119 | func IFFT2(x [][]complex128) [][]complex128 { 120 | return computeFFT2(x, IFFT) 121 | } 122 | 123 | func computeFFT2(x [][]complex128, fftFunc func([]complex128) []complex128) [][]complex128 { 124 | rows := len(x) 125 | if rows == 0 { 126 | panic("empty input array") 127 | } 128 | 129 | cols := len(x[0]) 130 | r := make([][]complex128, rows) 131 | for i := 0; i < rows; i++ { 132 | if len(x[i]) != cols { 133 | panic("ragged input array") 134 | } 135 | r[i] = make([]complex128, cols) 136 | } 137 | 138 | for i := 0; i < cols; i++ { 139 | t := make([]complex128, rows) 140 | for j := 0; j < rows; j++ { 141 | t[j] = x[j][i] 142 | } 143 | 144 | for n, v := range fftFunc(t) { 145 | r[n][i] = v 146 | } 147 | } 148 | 149 | for n, v := range r { 150 | r[n] = fftFunc(v) 151 | } 152 | 153 | return r 154 | } 155 | 156 | // FFTN returns the forward FFT of the matrix m, computed in all N dimensions. 157 | func FFTN(m *dsputils.Matrix) *dsputils.Matrix { 158 | return computeFFTN(m, FFT) 159 | } 160 | 161 | // IFFTN returns the forward FFT of the matrix m, computed in all N dimensions. 162 | func IFFTN(m *dsputils.Matrix) *dsputils.Matrix { 163 | return computeFFTN(m, IFFT) 164 | } 165 | 166 | func computeFFTN(m *dsputils.Matrix, fftFunc func([]complex128) []complex128) *dsputils.Matrix { 167 | dims := m.Dimensions() 168 | t := m.Copy() 169 | r := dsputils.MakeEmptyMatrix(dims) 170 | 171 | for n := range dims { 172 | dims[n] -= 1 173 | } 174 | 175 | for n := range dims { 176 | d := make([]int, len(dims)) 177 | copy(d, dims) 178 | d[n] = -1 179 | 180 | for { 181 | r.SetDim(fftFunc(t.Dim(d)), d) 182 | 183 | if !decrDim(d, dims) { 184 | break 185 | } 186 | } 187 | 188 | r, t = t, r 189 | } 190 | 191 | return t 192 | } 193 | 194 | // decrDim decrements an element of x by 1, skipping all -1s, and wrapping up to d. 195 | // If a value is 0, it will be reset to its correspending value in d, and will carry one from the next non -1 value to the right. 196 | // Returns true if decremented, else false. 197 | func decrDim(x, d []int) bool { 198 | for n, v := range x { 199 | if v == -1 { 200 | continue 201 | } else if v == 0 { 202 | i := n 203 | // find the next element to decrement 204 | for ; i < len(x); i++ { 205 | if x[i] == -1 { 206 | continue 207 | } else if x[i] == 0 { 208 | x[i] = d[i] 209 | } else { 210 | x[i] -= 1 211 | return true 212 | } 213 | } 214 | 215 | // no decrement 216 | return false 217 | } else { 218 | x[n] -= 1 219 | return true 220 | } 221 | } 222 | 223 | return false 224 | } 225 | -------------------------------------------------------------------------------- /fft/fft_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package fft 18 | 19 | import ( 20 | "fmt" 21 | "math" 22 | "math/cmplx" 23 | "runtime" 24 | "testing" 25 | 26 | "github.com/mjibson/go-dsp/dsputils" 27 | ) 28 | 29 | const ( 30 | sqrt2_2 = math.Sqrt2 / 2 31 | ) 32 | 33 | type fftTest struct { 34 | in []float64 35 | out []complex128 36 | } 37 | 38 | var fftTests = []fftTest{ 39 | // impulse responses 40 | { 41 | []float64{1}, 42 | []complex128{complex(1, 0)}, 43 | }, 44 | { 45 | []float64{1, 0}, 46 | []complex128{complex(1, 0), complex(1, 0)}, 47 | }, 48 | { 49 | []float64{1, 0, 0, 0}, 50 | []complex128{complex(1, 0), complex(1, 0), complex(1, 0), complex(1, 0)}, 51 | }, 52 | { 53 | []float64{1, 0, 0, 0, 0, 0, 0, 0}, 54 | []complex128{ 55 | complex(1, 0), 56 | complex(1, 0), 57 | complex(1, 0), 58 | complex(1, 0), 59 | complex(1, 0), 60 | complex(1, 0), 61 | complex(1, 0), 62 | complex(1, 0)}, 63 | }, 64 | 65 | // shifted impulse response 66 | { 67 | []float64{0, 1}, 68 | []complex128{complex(1, 0), complex(-1, 0)}, 69 | }, 70 | { 71 | []float64{0, 1, 0, 0}, 72 | []complex128{complex(1, 0), complex(0, -1), complex(-1, 0), complex(0, 1)}, 73 | }, 74 | { 75 | []float64{0, 1, 0, 0, 0, 0, 0, 0}, 76 | []complex128{ 77 | complex(1, 0), 78 | complex(sqrt2_2, -sqrt2_2), 79 | complex(0, -1), 80 | complex(-sqrt2_2, -sqrt2_2), 81 | complex(-1, 0), 82 | complex(-sqrt2_2, sqrt2_2), 83 | complex(0, 1), 84 | complex(sqrt2_2, sqrt2_2)}, 85 | }, 86 | 87 | // other 88 | { 89 | []float64{1, 2, 3, 4}, 90 | []complex128{ 91 | complex(10, 0), 92 | complex(-2, 2), 93 | complex(-2, 0), 94 | complex(-2, -2)}, 95 | }, 96 | { 97 | []float64{1, 3, 5, 7}, 98 | []complex128{ 99 | complex(16, 0), 100 | complex(-4, 4), 101 | complex(-4, 0), 102 | complex(-4, -4)}, 103 | }, 104 | { 105 | []float64{1, 2, 3, 4, 5, 6, 7, 8}, 106 | []complex128{ 107 | complex(36, 0), 108 | complex(-4, 9.65685425), 109 | complex(-4, 4), 110 | complex(-4, 1.65685425), 111 | complex(-4, 0), 112 | complex(-4, -1.65685425), 113 | complex(-4, -4), 114 | complex(-4, -9.65685425)}, 115 | }, 116 | 117 | // non power of 2 lengths 118 | { 119 | []float64{1, 0, 0, 0, 0}, 120 | []complex128{ 121 | complex(1, 0), 122 | complex(1, 0), 123 | complex(1, 0), 124 | complex(1, 0), 125 | complex(1, 0)}, 126 | }, 127 | { 128 | []float64{1, 2, 3}, 129 | []complex128{ 130 | complex(6, 0), 131 | complex(-1.5, 0.8660254), 132 | complex(-1.5, -0.8660254)}, 133 | }, 134 | { 135 | []float64{1, 1, 1}, 136 | []complex128{ 137 | complex(3, 0), 138 | complex(0, 0), 139 | complex(0, 0)}, 140 | }, 141 | } 142 | 143 | type fft2Test struct { 144 | in [][]float64 145 | out [][]complex128 146 | } 147 | 148 | var fft2Tests = []fft2Test{ 149 | { 150 | [][]float64{{1, 2, 3}, {3, 4, 5}}, 151 | [][]complex128{ 152 | {complex(18, 0), complex(-3, 1.73205081), complex(-3, -1.73205081)}, 153 | {complex(-6, 0), complex(0, 0), complex(0, 0)}}, 154 | }, 155 | { 156 | [][]float64{{0.1, 0.2, 0.3, 0.4, 0.5}, {1, 2, 3, 4, 5}, {3, 2, 1, 0, -1}}, 157 | [][]complex128{ 158 | {complex(21.5, 0), complex(-0.25, 0.34409548), complex(-0.25, 0.08122992), complex(-0.25, -0.08122992), complex(-0.25, -0.34409548)}, 159 | {complex(-8.5, -8.66025404), complex(5.70990854, 4.6742225), complex(1.15694356, 4.41135694), complex(-1.65694356, 4.24889709), complex(-6.20990854, 3.98603154)}, 160 | {complex(-8.5, 8.66025404), complex(-6.20990854, -3.98603154), complex(-1.65694356, -4.24889709), complex(1.15694356, -4.41135694), complex(5.70990854, -4.6742225)}}, 161 | }, 162 | } 163 | 164 | type fftnTest struct { 165 | in []float64 166 | dim []int 167 | out []complex128 168 | } 169 | 170 | var fftnTests = []fftnTest{ 171 | { 172 | []float64{4, 2, 3, 8, 5, 6, 7, 2, 13, 24, 13, 17}, 173 | []int{2, 2, 3}, 174 | []complex128{ 175 | complex(104, 0), complex(12.5, 14.72243186), complex(12.5, -14.72243186), 176 | complex(-42, 0), complex(-10.5, 6.06217783), complex(-10.5, -6.06217783), 177 | 178 | complex(-48, 0), complex(-4.5, -11.25833025), complex(-4.5, 11.25833025), 179 | complex(22, 0), complex(8.5, -6.06217783), complex(8.5, 6.06217783)}, 180 | }, 181 | } 182 | 183 | type reverseBitsTest struct { 184 | in uint 185 | sz uint 186 | out uint 187 | } 188 | 189 | var reverseBitsTests = []reverseBitsTest{ 190 | {0, 1, 0}, 191 | {1, 2, 2}, 192 | {1, 4, 8}, 193 | {2, 4, 4}, 194 | {3, 4, 12}, 195 | } 196 | 197 | func TestFFT(t *testing.T) { 198 | for _, ft := range fftTests { 199 | v := FFTReal(ft.in) 200 | if !dsputils.PrettyCloseC(v, ft.out) { 201 | t.Error("FFT error\ninput:", ft.in, "\noutput:", v, "\nexpected:", ft.out) 202 | } 203 | 204 | vi := IFFT(ft.out) 205 | if !dsputils.PrettyCloseC(vi, dsputils.ToComplex(ft.in)) { 206 | t.Error("IFFT error\ninput:", ft.out, "\noutput:", vi, "\nexpected:", dsputils.ToComplex(ft.in)) 207 | } 208 | } 209 | } 210 | 211 | func TestFFT2(t *testing.T) { 212 | for _, ft := range fft2Tests { 213 | v := FFT2Real(ft.in) 214 | if !dsputils.PrettyClose2(v, ft.out) { 215 | t.Error("FFT2 error\ninput:", ft.in, "\noutput:", v, "\nexpected:", ft.out) 216 | } 217 | 218 | vi := IFFT2(ft.out) 219 | if !dsputils.PrettyClose2(vi, dsputils.ToComplex2(ft.in)) { 220 | t.Error("IFFT2 error\ninput:", ft.out, "\noutput:", vi, "\nexpected:", dsputils.ToComplex2(ft.in)) 221 | } 222 | } 223 | } 224 | 225 | func TestFFTN(t *testing.T) { 226 | for _, ft := range fftnTests { 227 | m := dsputils.MakeMatrix(dsputils.ToComplex(ft.in), ft.dim) 228 | o := dsputils.MakeMatrix(ft.out, ft.dim) 229 | v := FFTN(m) 230 | if !v.PrettyClose(o) { 231 | t.Error("FFTN error\ninput:", m, "\noutput:", v, "\nexpected:", o) 232 | } 233 | 234 | vi := IFFTN(o) 235 | if !vi.PrettyClose(m) { 236 | t.Error("IFFTN error\ninput:", o, "\noutput:", vi, "\nexpected:", m) 237 | } 238 | } 239 | } 240 | 241 | func TestReverseBits(t *testing.T) { 242 | for _, rt := range reverseBitsTests { 243 | v := reverseBits(rt.in, rt.sz) 244 | 245 | if v != rt.out { 246 | t.Error("reverse bits error\ninput:", rt.in, "\nsize:", rt.sz, "\noutput:", v, "\nexpected:", rt.out) 247 | } 248 | } 249 | } 250 | 251 | func TestFFTMulti(t *testing.T) { 252 | N := 1 << 8 253 | a := make([]complex128, N) 254 | for i := 0; i < N; i++ { 255 | a[i] = complex(float64(i)/float64(N), 0) 256 | } 257 | 258 | FFT(a) 259 | } 260 | 261 | // run with: go test -test.bench="." 262 | func BenchmarkFFT(b *testing.B) { 263 | b.StopTimer() 264 | 265 | runtime.GOMAXPROCS(runtime.NumCPU()) 266 | 267 | N := 1 << 20 268 | a := make([]complex128, N) 269 | for i := 0; i < N; i++ { 270 | a[i] = complex(float64(i)/float64(N), 0) 271 | } 272 | 273 | EnsureRadix2Factors(N) 274 | 275 | b.StartTimer() 276 | 277 | for i := 0; i < b.N; i++ { 278 | FFT(a) 279 | } 280 | } 281 | 282 | // This example is adapted from Richard Lyon's "Understanding Digital Signal Processing," section 3.1.1. 283 | func ExampleFFTReal() { 284 | numSamples := 8 285 | 286 | // Equation 3-10. 287 | x := func(n int) float64 { 288 | wave0 := math.Sin(2.0 * math.Pi * float64(n) / 8.0) 289 | wave1 := 0.5 * math.Sin(2*math.Pi*float64(n)/4.0+3.0*math.Pi/4.0) 290 | return wave0 + wave1 291 | } 292 | 293 | // Discretize our function by sampling at 8 points. 294 | a := make([]float64, numSamples) 295 | for i := 0; i < numSamples; i++ { 296 | a[i] = x(i) 297 | } 298 | 299 | X := FFTReal(a) 300 | 301 | // Print the magnitude and phase at each frequency. 302 | for i := 0; i < numSamples; i++ { 303 | r, θ := cmplx.Polar(X[i]) 304 | θ *= 360.0 / (2 * math.Pi) 305 | if dsputils.Float64Equal(r, 0) { 306 | θ = 0 // (When the magnitude is close to 0, the angle is meaningless) 307 | } 308 | fmt.Printf("X(%d) = %.1f ∠ %.1f°\n", i, r, θ) 309 | } 310 | 311 | // Output: 312 | // X(0) = 0.0 ∠ 0.0° 313 | // X(1) = 4.0 ∠ -90.0° 314 | // X(2) = 2.0 ∠ 45.0° 315 | // X(3) = 0.0 ∠ 0.0° 316 | // X(4) = 0.0 ∠ 0.0° 317 | // X(5) = 0.0 ∠ 0.0° 318 | // X(6) = 2.0 ∠ -45.0° 319 | // X(7) = 4.0 ∠ 90.0° 320 | } 321 | -------------------------------------------------------------------------------- /fft/radix2.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package fft 18 | 19 | import ( 20 | "math" 21 | "runtime" 22 | "sync" 23 | ) 24 | 25 | var ( 26 | radix2Lock sync.RWMutex 27 | radix2Factors = map[int][]complex128{ 28 | 4: {complex(1, 0), complex(0, -1), complex(-1, 0), complex(0, 1)}, 29 | } 30 | ) 31 | 32 | // EnsureRadix2Factors ensures that all radix 2 factors are computed for inputs 33 | // of length input_len. This is used to precompute needed factors for known 34 | // sizes. Generally should only be used for benchmarks. 35 | func EnsureRadix2Factors(input_len int) { 36 | getRadix2Factors(input_len) 37 | } 38 | 39 | func getRadix2Factors(input_len int) []complex128 { 40 | radix2Lock.RLock() 41 | 42 | if hasRadix2Factors(input_len) { 43 | defer radix2Lock.RUnlock() 44 | return radix2Factors[input_len] 45 | } 46 | 47 | radix2Lock.RUnlock() 48 | radix2Lock.Lock() 49 | defer radix2Lock.Unlock() 50 | 51 | if !hasRadix2Factors(input_len) { 52 | for i, p := 8, 4; i <= input_len; i, p = i<<1, i { 53 | if radix2Factors[i] == nil { 54 | radix2Factors[i] = make([]complex128, i) 55 | 56 | for n, j := 0, 0; n < i; n, j = n+2, j+1 { 57 | radix2Factors[i][n] = radix2Factors[p][j] 58 | } 59 | 60 | for n := 1; n < i; n += 2 { 61 | sin, cos := math.Sincos(-2 * math.Pi / float64(i) * float64(n)) 62 | radix2Factors[i][n] = complex(cos, sin) 63 | } 64 | } 65 | } 66 | } 67 | 68 | return radix2Factors[input_len] 69 | } 70 | 71 | func hasRadix2Factors(idx int) bool { 72 | return radix2Factors[idx] != nil 73 | } 74 | 75 | type fft_work struct { 76 | start, end int 77 | } 78 | 79 | // radix2FFT returns the FFT calculated using the radix-2 DIT Cooley-Tukey algorithm. 80 | func radix2FFT(x []complex128) []complex128 { 81 | lx := len(x) 82 | factors := getRadix2Factors(lx) 83 | 84 | t := make([]complex128, lx) // temp 85 | r := reorderData(x) 86 | 87 | var blocks, stage, s_2 int 88 | 89 | jobs := make(chan *fft_work, lx) 90 | wg := sync.WaitGroup{} 91 | 92 | num_workers := worker_pool_size 93 | if (num_workers) == 0 { 94 | num_workers = runtime.GOMAXPROCS(0) 95 | } 96 | 97 | idx_diff := lx / num_workers 98 | if idx_diff < 2 { 99 | idx_diff = 2 100 | } 101 | 102 | worker := func() { 103 | for work := range jobs { 104 | for nb := work.start; nb < work.end; nb += stage { 105 | if stage != 2 { 106 | for j := 0; j < s_2; j++ { 107 | idx := j + nb 108 | idx2 := idx + s_2 109 | ridx := r[idx] 110 | w_n := r[idx2] * factors[blocks*j] 111 | t[idx] = ridx + w_n 112 | t[idx2] = ridx - w_n 113 | } 114 | } else { 115 | n1 := nb + 1 116 | rn := r[nb] 117 | rn1 := r[n1] 118 | t[nb] = rn + rn1 119 | t[n1] = rn - rn1 120 | } 121 | } 122 | wg.Done() 123 | } 124 | } 125 | 126 | for i := 0; i < num_workers; i++ { 127 | go worker() 128 | } 129 | defer close(jobs) 130 | 131 | for stage = 2; stage <= lx; stage <<= 1 { 132 | blocks = lx / stage 133 | s_2 = stage / 2 134 | 135 | for start, end := 0, stage; ; { 136 | if end-start >= idx_diff || end == lx { 137 | wg.Add(1) 138 | jobs <- &fft_work{start, end} 139 | 140 | if end == lx { 141 | break 142 | } 143 | 144 | start = end 145 | } 146 | 147 | end += stage 148 | } 149 | wg.Wait() 150 | r, t = t, r 151 | } 152 | 153 | return r 154 | } 155 | 156 | // reorderData returns a copy of x reordered for the DFT. 157 | func reorderData(x []complex128) []complex128 { 158 | lx := uint(len(x)) 159 | r := make([]complex128, lx) 160 | s := log2(lx) 161 | 162 | var n uint 163 | for ; n < lx; n++ { 164 | r[reverseBits(n, s)] = x[n] 165 | } 166 | 167 | return r 168 | } 169 | 170 | // log2 returns the log base 2 of v 171 | // from: http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious 172 | func log2(v uint) uint { 173 | var r uint 174 | 175 | for v >>= 1; v != 0; v >>= 1 { 176 | r++ 177 | } 178 | 179 | return r 180 | } 181 | 182 | // reverseBits returns the first s bits of v in reverse order 183 | // from: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious 184 | func reverseBits(v, s uint) uint { 185 | var r uint 186 | 187 | // Since we aren't reversing all the bits in v (just the first s bits), 188 | // we only need the first bit of v instead of a full copy. 189 | r = v & 1 190 | s-- 191 | 192 | for v >>= 1; v != 0; v >>= 1 { 193 | r <<= 1 194 | r |= v & 1 195 | s-- 196 | } 197 | 198 | return r << s 199 | } 200 | -------------------------------------------------------------------------------- /spectral/pwelch.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spectral 18 | 19 | import ( 20 | "math" 21 | "math/cmplx" 22 | 23 | "github.com/mjibson/go-dsp/dsputils" 24 | "github.com/mjibson/go-dsp/fft" 25 | "github.com/mjibson/go-dsp/window" 26 | ) 27 | 28 | type PwelchOptions struct { 29 | // NFFT is the number of data points used in each block for the FFT. Must be 30 | // even; a power 2 is most efficient. This should *NOT* be used to get zero 31 | // padding, or the scaling of the result will be incorrect. Use Pad for 32 | // this instead. 33 | // 34 | // The default value is 256. 35 | NFFT int 36 | 37 | // Window is a function that returns an array of window values the length 38 | // of its input parameter. Each segment is scaled by these values. 39 | // 40 | // The default (nil) is window.Hann, from the go-dsp/window package. 41 | Window func(int) []float64 42 | 43 | // Pad is the number of points to which the data segment is padded when 44 | // performing the FFT. This can be different from NFFT, which specifies the 45 | // number of data points used. While not increasing the actual resolution of 46 | // the psd (the minimum distance between resolvable peaks), this can give 47 | // more points in the plot, allowing for more detail. 48 | // 49 | // The value default is 0, which sets Pad equal to NFFT. 50 | Pad int 51 | 52 | // Noverlap is the number of points of overlap between blocks. 53 | // 54 | // The default value is 0 (no overlap). 55 | Noverlap int 56 | 57 | // Specifies whether the resulting density values should be scaled by the 58 | // scaling frequency, which gives density in units of Hz^-1. This allows for 59 | // integration over the returned frequency values. The default is set for 60 | // MATLAB compatibility. Note that this is the opposite of matplotlib style, 61 | // but with equivalent defaults. 62 | // 63 | // The default value is false (enable scaling). 64 | Scale_off bool 65 | } 66 | 67 | // Pwelch estimates the power spectral density of x using Welch's method. 68 | // Fs is the sampling frequency (samples per time unit) of x. Fs is used 69 | // to calculate freqs. 70 | // Returns the power spectral density Pxx and corresponding frequencies freqs. 71 | // Designed to be similar to the matplotlib implementation below. 72 | // Reference: http://matplotlib.org/api/mlab_api.html#matplotlib.mlab.psd 73 | // See also: http://www.mathworks.com/help/signal/ref/pwelch.html 74 | func Pwelch(x []float64, Fs float64, o *PwelchOptions) (Pxx, freqs []float64) { 75 | if len(x) == 0 { 76 | return []float64{}, []float64{} 77 | } 78 | 79 | nfft := o.NFFT 80 | pad := o.Pad 81 | noverlap := o.Noverlap 82 | wf := o.Window 83 | enable_scaling := !o.Scale_off 84 | 85 | if nfft == 0 { 86 | nfft = 256 87 | } 88 | 89 | if wf == nil { 90 | wf = window.Hann 91 | } 92 | 93 | if pad == 0 { 94 | pad = nfft 95 | } 96 | 97 | if len(x) < nfft { 98 | x = dsputils.ZeroPadF(x, nfft) 99 | } 100 | 101 | lp := pad/2 + 1 102 | var scale float64 = 2 103 | 104 | segs := Segment(x, nfft, noverlap) 105 | 106 | Pxx = make([]float64, lp) 107 | for _, x := range segs { 108 | x = dsputils.ZeroPadF(x, pad) 109 | window.Apply(x, wf) 110 | 111 | pgram := fft.FFTReal(x) 112 | 113 | for j := range Pxx { 114 | d := real(cmplx.Conj(pgram[j])*pgram[j]) / float64(len(segs)) 115 | 116 | if j > 0 && j < lp-1 { 117 | d *= scale 118 | } 119 | 120 | Pxx[j] += d 121 | } 122 | } 123 | 124 | w := wf(nfft) 125 | var norm float64 126 | for _, x := range w { 127 | norm += math.Pow(x, 2) 128 | } 129 | 130 | if enable_scaling { 131 | norm *= Fs 132 | } 133 | 134 | for i := range Pxx { 135 | Pxx[i] /= norm 136 | } 137 | 138 | freqs = make([]float64, lp) 139 | coef := Fs / float64(pad) 140 | for i := range freqs { 141 | freqs[i] = float64(i) * coef 142 | } 143 | 144 | return 145 | } 146 | -------------------------------------------------------------------------------- /spectral/pwelch_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spectral 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/mjibson/go-dsp/dsputils" 23 | ) 24 | 25 | type pwelchTest struct { 26 | fs float64 27 | opts *PwelchOptions 28 | x, p, freqs []float64 29 | } 30 | 31 | var pwelchTests = []pwelchTest{ 32 | { // zero-length input 33 | 0, 34 | &PwelchOptions{}, 35 | []float64{}, 36 | []float64{}, 37 | []float64{}, 38 | }, 39 | { 40 | 2, 41 | &PwelchOptions{}, 42 | []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}, 43 | []float64{3.66817103e+04, 6.16097526e+04, 3.70964854e+04, 1.76858083e+04, 8.82747121e+03, 5.58636625e+03, 3.86686565e+03, 2.79695091e+03, 2.14687978e+03, 1.68918004e+03, 1.36571705e+03, 1.13024093e+03, 9.48033939e+02, 8.08850444e+02, 6.97809757e+02, 6.08092372e+02, 5.35404251e+02, 4.74620274e+02, 4.24037212e+02, 3.81226909e+02, 3.44548926e+02, 3.13192558e+02, 2.85886182e+02, 2.62122493e+02, 2.41303266e+02, 2.22870690e+02, 2.06594463e+02, 1.92060902e+02, 1.79062190e+02, 1.67411631e+02, 1.56878696e+02, 1.47375046e+02, 1.38742768e+02, 1.30879468e+02, 1.23716560e+02, 1.17146757e+02, 1.11127186e+02, 1.05591138e+02, 1.00482309e+02, 9.57717459e+01, 9.14056404e+01, 8.73592894e+01, 8.36025117e+01, 8.01022290e+01, 7.68443525e+01, 7.38005900e+01, 7.09550385e+01, 6.82933042e+01, 6.57951735e+01, 6.34526724e+01, 6.12504908e+01, 5.91777124e+01, 5.72271084e+01, 5.53860529e+01, 5.36493451e+01, 5.20085636e+01, 5.04559628e+01, 4.89876620e+01, 4.75956980e+01, 4.62762918e+01, 4.50247688e+01, 4.38355570e+01, 4.27063668e+01, 4.16321728e+01, 4.06100428e+01, 3.96373615e+01, 3.87101146e+01, 3.78267782e+01, 3.69842029e+01, 3.61800421e+01, 3.54128094e+01, 3.46796320e+01, 3.39793658e+01, 3.33100629e+01, 3.26698301e+01, 3.20577904e+01, 3.14719152e+01, 3.09112634e+01, 3.03746526e+01, 2.98605643e+01, 2.93684407e+01, 2.88968774e+01, 2.84450603e+01, 2.80122875e+01, 2.75973586e+01, 2.71998759e+01, 2.68188936e+01, 2.64536948e+01, 2.61038720e+01, 2.57684964e+01, 2.54472465e+01, 2.51395088e+01, 2.48446551e+01, 2.45624511e+01, 2.42921985e+01, 2.40336109e+01, 2.37863119e+01, 2.35497603e+01, 2.33238184e+01, 2.31079809e+01, 2.29019795e+01, 2.27056035e+01, 2.25183990e+01, 2.23402769e+01, 2.21708920e+01, 2.20099898e+01, 2.18574728e+01, 2.17129732e+01, 2.15764231e+01, 2.14476081e+01, 2.13262901e+01, 2.12124459e+01, 2.11057929e+01, 2.10062684e+01, 2.09137648e+01, 2.08280657e+01, 2.07491945e+01, 2.06769518e+01, 2.06112729e+01, 2.05521368e+01, 2.04993557e+01, 2.04529802e+01, 2.04128917e+01, 2.03790224e+01, 2.03514209e+01, 2.03299362e+01, 2.03146325e+01, 2.03054705e+01, 1.01511907e+01}, 44 | []float64{0, 0.0078125, 0.015625, 0.0234375, 0.03125, 0.0390625, 0.046875, 0.0546875, 0.0625, 0.0703125, 0.078125, 0.0859375, 0.09375, 0.1015625, 0.109375, 0.1171875, 0.125, 0.1328125, 0.140625, 0.1484375, 0.15625, 0.1640625, 0.171875, 0.1796875, 0.1875, 0.1953125, 0.203125, 0.2109375, 0.21875, 0.2265625, 0.234375, 0.2421875, 0.25, 0.2578125, 0.265625, 0.2734375, 0.28125, 0.2890625, 0.296875, 0.3046875, 0.3125, 0.3203125, 0.328125, 0.3359375, 0.34375, 0.3515625, 0.359375, 0.3671875, 0.375, 0.3828125, 0.390625, 0.3984375, 0.40625, 0.4140625, 0.421875, 0.4296875, 0.4375, 0.4453125, 0.453125, 0.4609375, 0.46875, 0.4765625, 0.484375, 0.4921875, 0.5, 0.5078125, 0.515625, 0.5234375, 0.53125, 0.5390625, 0.546875, 0.5546875, 0.5625, 0.5703125, 0.578125, 0.5859375, 0.59375, 0.6015625, 0.609375, 0.6171875, 0.625, 0.6328125, 0.640625, 0.6484375, 0.65625, 0.6640625, 0.671875, 0.6796875, 0.6875, 0.6953125, 0.703125, 0.7109375, 0.71875, 0.7265625, 0.734375, 0.7421875, 0.75, 0.7578125, 0.765625, 0.7734375, 0.78125, 0.7890625, 0.796875, 0.8046875, 0.8125, 0.8203125, 0.828125, 0.8359375, 0.84375, 0.8515625, 0.859375, 0.8671875, 0.875, 0.8828125, 0.890625, 0.8984375, 0.90625, 0.9140625, 0.921875, 0.9296875, 0.9375, 0.9453125, 0.953125, 0.9609375, 0.96875, 0.9765625, 0.984375, 0.9921875, 1}, 45 | }, 46 | } 47 | 48 | func TestPwelch(t *testing.T) { 49 | 50 | for _, v := range pwelchTests { 51 | p, freqs := Pwelch(v.x, v.fs, v.opts) 52 | if !dsputils.PrettyClose(p, v.p) { 53 | t.Error("Pwelch Pxx error\n input:", v.x, "\n output:", p, "\nexpected:", v.p) 54 | } 55 | 56 | if !dsputils.PrettyClose(freqs, v.freqs) { 57 | t.Error("Pwelch freqs error\n input:", v.x, "\n output:", freqs, "\nexpected:", v.freqs) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spectral/spectral.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | // Package spectral provides spectral analysis functions for digital signal processing. 18 | package spectral 19 | 20 | // Segment x segmented into segments of length size with specified noverlap. 21 | // Number of segments returned is (len(x) - size) / (size - noverlap) + 1. 22 | func Segment(x []float64, size, noverlap int) [][]float64 { 23 | stride := size - noverlap 24 | lx := len(x) 25 | 26 | var segments int 27 | if lx == size { 28 | segments = 1 29 | } else if lx > size { 30 | segments = (len(x)-size)/stride + 1 31 | } else { 32 | segments = 0 33 | } 34 | 35 | r := make([][]float64, segments) 36 | for i, offset := 0, 0; i < segments; i++ { 37 | r[i] = make([]float64, size) 38 | 39 | for j := 0; j < size; j++ { 40 | r[i][j] = x[offset+j] 41 | } 42 | 43 | offset += stride 44 | } 45 | 46 | return r 47 | } 48 | -------------------------------------------------------------------------------- /spectral/spectral_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spectral 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/mjibson/go-dsp/dsputils" 23 | ) 24 | 25 | type segmentTest struct { 26 | size, 27 | noverlap int 28 | out [][]float64 29 | } 30 | 31 | var segmentTests = []segmentTest{ 32 | { 33 | 4, 0, 34 | [][]float64{ 35 | {1, 2, 3, 4}, 36 | {5, 6, 7, 8}, 37 | }, 38 | }, 39 | { 40 | 4, 1, 41 | [][]float64{ 42 | {1, 2, 3, 4}, 43 | {4, 5, 6, 7}, 44 | {7, 8, 9, 10}, 45 | }, 46 | }, 47 | { 48 | 4, 2, 49 | [][]float64{ 50 | {1, 2, 3, 4}, 51 | {3, 4, 5, 6}, 52 | {5, 6, 7, 8}, 53 | {7, 8, 9, 10}, 54 | }, 55 | }, 56 | } 57 | 58 | func TestSegment(t *testing.T) { 59 | x := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 60 | 61 | for _, v := range segmentTests { 62 | o := Segment(x, v.size, v.noverlap) 63 | if !dsputils.PrettyClose2F(o, v.out) { 64 | t.Error("Segment error\n output:", o, "\nexpected:", v.out) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /wav/float.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maddyblue/go-dsp/11479a337f1259210b7c8f93f7bf2b0cc87b066e/wav/float.wav -------------------------------------------------------------------------------- /wav/small.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maddyblue/go-dsp/11479a337f1259210b7c8f93f7bf2b0cc87b066e/wav/small.wav -------------------------------------------------------------------------------- /wav/wav.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | // Package wav provides support for the WAV file format. 18 | // 19 | // Supported formats are PCM 8- and 16-bit, and IEEE float. Extended chunks 20 | // (JUNK, bext, and others added by tools like ProTools) are ignored. 21 | package wav 22 | 23 | import ( 24 | "bytes" 25 | "encoding/binary" 26 | "fmt" 27 | "io" 28 | "io/ioutil" 29 | "math" 30 | "time" 31 | ) 32 | 33 | const ( 34 | wavFormatPCM = 1 35 | wavFormatIEEEFloat = 3 36 | ) 37 | 38 | // Header contains Wav fmt chunk data. 39 | type Header struct { 40 | AudioFormat uint16 41 | NumChannels uint16 42 | SampleRate uint32 43 | ByteRate uint32 44 | BlockAlign uint16 45 | BitsPerSample uint16 46 | } 47 | 48 | // Wav reads wav files. 49 | type Wav struct { 50 | Header 51 | // Samples is the total number of available samples. 52 | Samples int 53 | // Duration is the estimated duration based on reported samples. 54 | Duration time.Duration 55 | 56 | r io.Reader 57 | } 58 | 59 | // New reads the WAV header from r. 60 | func New(r io.Reader) (*Wav, error) { 61 | var w Wav 62 | header := make([]byte, 16) 63 | if _, err := io.ReadFull(r, header[:12]); err != nil { 64 | return nil, err 65 | } 66 | if string(header[0:4]) != "RIFF" { 67 | return nil, fmt.Errorf("wav: missing RIFF") 68 | } 69 | if string(header[8:12]) != "WAVE" { 70 | return nil, fmt.Errorf("wav: missing WAVE") 71 | } 72 | hasFmt := false 73 | for { 74 | if _, err := io.ReadFull(r, header[:8]); err != nil { 75 | return nil, err 76 | } 77 | sz := binary.LittleEndian.Uint32(header[4:]) 78 | switch typ := string(header[:4]); typ { 79 | case "fmt ": 80 | if sz < 16 { 81 | return nil, fmt.Errorf("wav: bad fmt size") 82 | } 83 | f := make([]byte, sz) 84 | if _, err := io.ReadFull(r, f); err != nil { 85 | return nil, err 86 | } 87 | if err := binary.Read(bytes.NewBuffer(f), binary.LittleEndian, &w.Header); err != nil { 88 | return nil, err 89 | } 90 | switch w.AudioFormat { 91 | case wavFormatPCM: 92 | case wavFormatIEEEFloat: 93 | default: 94 | return nil, fmt.Errorf("wav: unknown audio format: %02x", w.AudioFormat) 95 | } 96 | hasFmt = true 97 | case "data": 98 | if !hasFmt { 99 | return nil, fmt.Errorf("wav: unexpected fmt chunk") 100 | } 101 | w.Samples = int(sz) / int(w.BitsPerSample) * 8 102 | w.Duration = time.Duration(w.Samples) * time.Second / time.Duration(w.SampleRate) / time.Duration(w.NumChannels) 103 | w.r = io.LimitReader(r, int64(sz)) 104 | return &w, nil 105 | default: 106 | io.CopyN(ioutil.Discard, r, int64(sz)) 107 | } 108 | } 109 | } 110 | 111 | // ReadSamples returns a [n]T, where T is uint8, int16, or float32, based on the 112 | // wav data. n is the number of samples to return. 113 | func (w *Wav) ReadSamples(n int) (interface{}, error) { 114 | var data interface{} 115 | switch w.AudioFormat { 116 | case wavFormatPCM: 117 | switch w.BitsPerSample { 118 | case 8: 119 | data = make([]uint8, n) 120 | case 16: 121 | data = make([]int16, n) 122 | default: 123 | return nil, fmt.Errorf("wav: unknown bits per sample: %v", w.BitsPerSample) 124 | } 125 | case wavFormatIEEEFloat: 126 | data = make([]float32, n) 127 | default: 128 | return nil, fmt.Errorf("wav: unknown audio format") 129 | } 130 | if err := binary.Read(w.r, binary.LittleEndian, data); err != nil { 131 | return nil, err 132 | } 133 | return data, nil 134 | } 135 | 136 | // ReadFloats is like ReadSamples, but it converts any underlying data to a 137 | // float32. 138 | func (w *Wav) ReadFloats(n int) ([]float32, error) { 139 | d, err := w.ReadSamples(n) 140 | if err != nil { 141 | return nil, err 142 | } 143 | var f []float32 144 | switch d := d.(type) { 145 | case []uint8: 146 | f = make([]float32, len(d)) 147 | for i, v := range d { 148 | f[i] = float32(v) / math.MaxUint8 149 | } 150 | case []int16: 151 | f = make([]float32, len(d)) 152 | for i, v := range d { 153 | f[i] = (float32(v) - math.MinInt16) / (math.MaxInt16 - math.MinInt16) 154 | } 155 | case []float32: 156 | f = d 157 | default: 158 | return nil, fmt.Errorf("wav: unknown type: %T", d) 159 | } 160 | return f, nil 161 | } 162 | -------------------------------------------------------------------------------- /wav/wav_test.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func checkHeader(b []byte) error { 11 | _, err := New(bytes.NewBuffer(b)) 12 | return err 13 | } 14 | 15 | func TestShortHeaderValidation(t *testing.T) { 16 | shortHeader := []byte{0x52, 0x49, 0x46, 0x46, 0x72, 0x8C, 0x34, 0x00, 0x57, 0x41, 0x56, 0x45} 17 | if err := checkHeader(shortHeader); err == nil { 18 | t.Fatal("Expected short header to fail validation, but validation passed") 19 | } 20 | if err := checkHeader(nil); err == nil { 21 | t.Fatal("Expected nil header to fail validation, but validation passed") 22 | } 23 | } 24 | 25 | func TestInvalidHeaderValidation(t *testing.T) { 26 | validateErrorForMissingHeaderValue := func(err error, value string) { 27 | if err == nil { 28 | t.Fatalf("Invalid header data missing '%s' should fail validation", value) 29 | } 30 | } 31 | 32 | // 44 empty bytes 33 | invalidHeader := make([]byte, 44) 34 | err := checkHeader(invalidHeader) 35 | validateErrorForMissingHeaderValue(err, "RIFF") 36 | 37 | // RIFF and 40 empty bytes 38 | _ = copy(invalidHeader[:4], []byte{0x52, 0x49, 0x46, 0x46}) 39 | err = checkHeader(invalidHeader) 40 | validateErrorForMissingHeaderValue(err, "WAVE") 41 | 42 | // RIFF, WAVE, and 36 empty bytes 43 | _ = copy(invalidHeader[8:12], []byte{0x57, 0x41, 0x56, 0x45}) 44 | err = checkHeader(invalidHeader) 45 | validateErrorForMissingHeaderValue(err, "fmt") 46 | 47 | // RIFF, WAVE, fmt, and 32 empty bytes 48 | _ = copy(invalidHeader[12:16], []byte{0x66, 0x6D, 0x74, 0x20}) 49 | err = checkHeader(invalidHeader) 50 | validateErrorForMissingHeaderValue(err, "data") 51 | } 52 | 53 | type wavTest struct { 54 | w Wav 55 | typ reflect.Type 56 | } 57 | 58 | func TestWav(t *testing.T) { 59 | wavTests := map[string]wavTest{ 60 | "small.wav": { 61 | w: Wav{ 62 | Header: Header{ 63 | AudioFormat: 1, 64 | NumChannels: 1, 65 | SampleRate: 44100, 66 | ByteRate: 88200, 67 | BlockAlign: 2, 68 | BitsPerSample: 16, 69 | }, 70 | Samples: 41888, 71 | Duration: 949841269, 72 | }, 73 | typ: reflect.TypeOf(make([]uint8, 0)), 74 | }, 75 | "float.wav": { 76 | w: Wav{ 77 | Header: Header{ 78 | AudioFormat: 3, 79 | NumChannels: 1, 80 | SampleRate: 44100, 81 | ByteRate: 176400, 82 | BlockAlign: 4, 83 | BitsPerSample: 32, 84 | }, 85 | Samples: 1889280 / 4, 86 | Duration: 10710204081, 87 | }, 88 | typ: reflect.TypeOf(make([]float32, 0)), 89 | }, 90 | } 91 | for name, wt := range wavTests { 92 | f, err := os.Open(name) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | defer f.Close() 97 | w, err := New(f) 98 | if err != nil { 99 | t.Fatalf("%v: %v", name, err) 100 | } 101 | if !eq(*w, wt.w) { 102 | t.Errorf("wavs not equal: %v\ngot: %v\nexpected: %v", name, w, &wt.w) 103 | } 104 | } 105 | } 106 | 107 | func eq(x, y Wav) bool { 108 | x.r, y.r = nil, nil 109 | return x == y 110 | } 111 | -------------------------------------------------------------------------------- /window/window.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | // Package window provides window functions for digital signal processing. 18 | package window 19 | 20 | import ( 21 | "math" 22 | ) 23 | 24 | // Apply applies the window windowFunction to x. 25 | func Apply(x []float64, windowFunction func(int) []float64) { 26 | for i, w := range windowFunction(len(x)) { 27 | x[i] *= w 28 | } 29 | } 30 | 31 | // Rectangular returns an L-point rectangular window (all values are 1). 32 | func Rectangular(L int) []float64 { 33 | r := make([]float64, L) 34 | 35 | for i := range r { 36 | r[i] = 1 37 | } 38 | 39 | return r 40 | } 41 | 42 | // Hamming returns an L-point symmetric Hamming window. 43 | // Reference: http://www.mathworks.com/help/signal/ref/hamming.html 44 | func Hamming(L int) []float64 { 45 | r := make([]float64, L) 46 | 47 | if L == 1 { 48 | r[0] = 1 49 | } else { 50 | N := L - 1 51 | coef := math.Pi * 2 / float64(N) 52 | for n := 0; n <= N; n++ { 53 | r[n] = 0.54 - 0.46*math.Cos(coef*float64(n)) 54 | } 55 | } 56 | 57 | return r 58 | } 59 | 60 | // Hann returns an L-point Hann window. 61 | // Reference: http://www.mathworks.com/help/signal/ref/hann.html 62 | func Hann(L int) []float64 { 63 | r := make([]float64, L) 64 | 65 | if L == 1 { 66 | r[0] = 1 67 | } else { 68 | N := L - 1 69 | coef := 2 * math.Pi / float64(N) 70 | for n := 0; n <= N; n++ { 71 | r[n] = 0.5 * (1 - math.Cos(coef*float64(n))) 72 | } 73 | } 74 | 75 | return r 76 | } 77 | 78 | // Bartlett returns an L-point Bartlett window. 79 | // Reference: http://www.mathworks.com/help/signal/ref/bartlett.html 80 | func Bartlett(L int) []float64 { 81 | r := make([]float64, L) 82 | 83 | if L == 1 { 84 | r[0] = 1 85 | } else { 86 | N := L - 1 87 | coef := 2 / float64(N) 88 | n := 0 89 | for ; n <= N/2; n++ { 90 | r[n] = coef * float64(n) 91 | } 92 | for ; n <= N; n++ { 93 | r[n] = 2 - coef*float64(n) 94 | } 95 | } 96 | 97 | return r 98 | } 99 | 100 | // FlatTop returns an L-point flat top window. 101 | // Reference: http://www.mathworks.com/help/signal/ref/flattopwin.html 102 | func FlatTop(L int) []float64 { 103 | const ( 104 | alpha0 = float64(0.21557895) 105 | alpha1 = float64(0.41663158) 106 | alpha2 = float64(0.277263158) 107 | alpha3 = float64(0.083578947) 108 | alpha4 = float64(0.006947368) 109 | ) 110 | 111 | r := make([]float64, L) 112 | 113 | if L == 1 { 114 | r[0] = 1 115 | return r 116 | } 117 | 118 | N := L - 1 119 | coef := 2 * math.Pi / float64(N) 120 | 121 | for n := 0; n <= N; n++ { 122 | factor := float64(n) * coef 123 | 124 | term0 := alpha0 125 | term1 := alpha1 * math.Cos(factor) 126 | term2 := alpha2 * math.Cos(2*factor) 127 | term3 := alpha3 * math.Cos(3*factor) 128 | term4 := alpha4 * math.Cos(4*factor) 129 | 130 | r[n] = term0 - term1 + term2 - term3 + term4 131 | } 132 | 133 | return r 134 | } 135 | 136 | // Blackman returns an L-point Blackman window 137 | // Reference: http://www.mathworks.com/help/signal/ref/blackman.html 138 | func Blackman(L int) []float64 { 139 | r := make([]float64, L) 140 | if L == 1 { 141 | r[0] = 1 142 | } else { 143 | N := L - 1 144 | for n := 0; n <= N; n++ { 145 | const term0 = 0.42 146 | term1 := -0.5 * math.Cos(2*math.Pi*float64(n)/float64(N)) 147 | term2 := 0.08 * math.Cos(4*math.Pi*float64(n)/float64(N)) 148 | r[n] = term0 + term1 + term2 149 | } 150 | } 151 | return r 152 | } 153 | -------------------------------------------------------------------------------- /window/window_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package window 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/mjibson/go-dsp/dsputils" 23 | ) 24 | 25 | type windowTest struct { 26 | in int 27 | hamming []float64 28 | hann []float64 29 | bartlett []float64 30 | flatTop []float64 31 | blackman []float64 32 | } 33 | 34 | var windowTests = []windowTest{ 35 | { 36 | 1, 37 | []float64{1}, 38 | []float64{1}, 39 | []float64{1}, 40 | []float64{1}, 41 | []float64{1}, 42 | }, 43 | { 44 | 5, 45 | []float64{0.08, 0.54, 1, 0.54, 0.08}, 46 | []float64{0, 0.5, 1, 0.5, 0}, 47 | []float64{0, 0.5, 1, 0.5, 0}, 48 | []float64{-0.0004210510000000013, -0.05473684000000003, 1, -0.05473684000000003, -0.0004210510000000013}, 49 | []float64{0, 0.34, 1, 0.34, 0}, 50 | }, 51 | { 52 | 10, 53 | []float64{0.08, 0.18761956, 0.46012184, 0.77, 0.97225861, 0.97225861, 0.77, 0.46012184, 0.18761956, 0.08}, 54 | []float64{0, 0.116977778440511, 0.413175911166535, 0.75, 0.969846310392954, 0.969846310392954, 0.75, 0.413175911166535, 0.116977778440511, 0}, 55 | []float64{0, 0.222222222222222, 0.444444444444444, 0.666666666666667, 0.888888888888889, 0.888888888888889, 0.666666666666667, 0.444444444444444, 0.222222222222222, 0}, 56 | []float64{-0.000421051000000, -0.020172031509486, -0.070199042063189, 0.198210530000000, 0.862476344072674, 0.862476344072674, 0.198210530000000, -0.070199042063189, -0.020172031509486, -0.000421051000000}, 57 | []float64{0, 0.0508696327, 0.258000502, 0.63, 0.951129866, 0.951129866, 0.63, 0.258000502, 0.0508696327, 0}, 58 | }, 59 | } 60 | 61 | func TestWindowFunctions(t *testing.T) { 62 | for _, v := range windowTests { 63 | o := Hamming(v.in) 64 | if !dsputils.PrettyClose(o, v.hamming) { 65 | t.Error("hamming error\ninput:", v.in, "\noutput:", o, "\nexpected:", v.hamming) 66 | } 67 | 68 | o = Hann(v.in) 69 | if !dsputils.PrettyClose(o, v.hann) { 70 | t.Error("hann error\ninput:", v.in, "\noutput:", o, "\nexpected:", v.hann) 71 | } 72 | 73 | o = Bartlett(v.in) 74 | if !dsputils.PrettyClose(o, v.bartlett) { 75 | t.Error("bartlett error\ninput:", v.in, "\noutput:", o, "\nexpected:", v.bartlett) 76 | } 77 | 78 | o = Rectangular(v.in) 79 | Apply(o, Hamming) 80 | if !dsputils.PrettyClose(o, v.hamming) { 81 | t.Error("apply error\noutput:", o, "\nexpected:", v.hamming) 82 | } 83 | 84 | o = FlatTop(v.in) 85 | if !dsputils.PrettyClose(o, v.flatTop) { 86 | t.Error("flatTop error\ninput:", v.in, "\noutput:", o, "\nexpected:", v.flatTop) 87 | } 88 | 89 | o = Blackman(v.in) 90 | if !dsputils.PrettyClose(o, v.blackman) { 91 | t.Error("blackman error\ninput:", v.in, "\noutput:", o, "\nexpected:", v.blackman) 92 | } 93 | } 94 | } 95 | --------------------------------------------------------------------------------