├── .gitignore ├── .travis.yml ├── Authors ├── License ├── README.md ├── RoadMap.md ├── convol ├── cmplx │ ├── convol.go │ ├── convol_test.go │ ├── doc.go │ ├── k.go │ ├── k_test.go │ ├── ola.go │ ├── ola_test.go │ ├── t.go │ ├── t_test.go │ └── unscale.go ├── convol.go ├── convol_test.go ├── doc.go ├── k.go ├── k_test.go ├── ola.go ├── ola_test.go ├── t.go ├── t_test.go └── unscale.go ├── czt ├── doc.go ├── t.go └── t_test.go ├── dct ├── cos.go ├── doc.go ├── naive.go ├── naive_test.go ├── plot.go ├── plot_test.go ├── t.go ├── t_test.go ├── z.go └── z_test.go ├── doc.go ├── fft ├── bin.go ├── bin_test.go ├── chirpz.go ├── chirpz_test.go ├── convolve_test.go ├── dilate_test.go ├── doc.go ├── factor.go ├── factor_test.go ├── fft.go ├── hc.go ├── hc_test.go ├── pad.go ├── r2.go ├── r2_test.go ├── real.go ├── real_test.go ├── revbin.go ├── revbin_test.go ├── s.go ├── s_test.go ├── scale.go ├── t.go ├── t_test.go ├── twiddle.go ├── twiddle_test.go └── zpad.go ├── go.mod ├── go.sum ├── lpc ├── README.md ├── doc.go ├── state.go ├── t.go └── t_test.go ├── mathutil └── qitp │ ├── abc.go │ ├── abc_test.go │ └── doc.go ├── resample ├── ct.go ├── ct_test.go ├── doc.go ├── itp.go └── itp_test.go └── wfn ├── blackman.go ├── doc.go ├── hamming.go ├── hann.go ├── lanczos.go ├── sinc.go ├── stretch.go └── t.go /.gitignore: -------------------------------------------------------------------------------- 1 | dct/*.png 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.10" 5 | - "tip" 6 | 7 | notifications: 8 | on_success: false 9 | on_failure: always 10 | 11 | install: go get -t zikichombo.org/dsp/... 12 | 13 | script: go test zikichombo.org/dsp/... 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Authors: -------------------------------------------------------------------------------- 1 | IRI France, SAS 2 | Robin Eklind 3 | Scott Cotton 4 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright 2018 The ZikiChombo Authors, as listed in the file "Authors" with this 2 | license. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of IRI France, SAS. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ZikiChombo](http://zikichombo.org) dsp project 2 | 3 | [![Build Status](https://travis-ci.com/zikichombo/dsp.svg?branch=master)](https://travis-ci.com/zikichombo/dsp) 4 | -------------------------------------------------------------------------------- /RoadMap.md: -------------------------------------------------------------------------------- 1 | # DSP RoadMap 2 | The code here represents a start to a broadly featured audio DSP library. 3 | 4 | However, there is lots of work to be done. Below is our RoadMap for much 5 | of this work. 6 | 7 | ## STFT 8 | ### Analysis 9 | #### Spectral features for analysis 10 | 1. Spectral Flux, Centroids, etc 11 | 1. Mel Spectrogram 12 | 1. Cepstrum 13 | ### Synthesis 14 | 1. Deal with windowing correctly. 15 | 1. PV 16 | 1. Spectral whitening 17 | 1. median filter 18 | ### Gabor Frames 19 | 20 | # Filtering 21 | ## FIR (feedforward) filtering toolbox 22 | ### Basic brick wall, band pass/stop FIR filtering design 23 | ### Implementation, using convolution for larger order filters 24 | ### Tools supporting window method design 25 | ### Tools supporting automatic design (eg Remez exchange) 26 | 27 | ## IIR (feedback) Filtering 28 | ### Biquad 29 | Simple filter design interface and implementation (need 30 | to decide which type(s) (II/IV?) to support. 31 | 1. Notch, 32 | 1. Peak, 33 | 1. Brick wall, 34 | 1. Shelf 35 | ### Parallel and Series composition 36 | ### Butterworth, etc (requires some general filtering tools below) 37 | 38 | ## General filtering tools (combined feedforward/feedback) 39 | ## like matlab freqz 40 | ## Pole Zero filter specification 41 | ## Bilinear transform 42 | ## Z transform and polynomial solver 43 | 44 | ## Perceptual filtering 45 | ### A weighting, etc 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /convol/cmplx/convol.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import "fmt" 7 | 8 | // Do performs linear convolution of a and b, placing 9 | // the result in a and returning it if there is space, 10 | // otherwise a new slice is allocated. 11 | // 12 | // To avoid the allocation, it should be that 13 | // 14 | // cap(a) >= len(a) + len(b) - 1 15 | // 16 | func Do(a, b []complex128) []complex128 { 17 | t := New(len(a), len(b)) 18 | a = t.WinA(a) 19 | b = t.WinB(b) 20 | res, e := t.Conv(a, b) 21 | if e != nil { 22 | panic(fmt.Sprintf("error %s\n", e)) 23 | } 24 | return res 25 | } 26 | 27 | // To performs linear convolution of a and b, placing 28 | // the result in dst and returning it. 29 | // 30 | // if dst does not have sufficient capacity, a new slice 31 | // is allocated and returned in its place. 32 | func To(dst, a, b []complex128) []complex128 { 33 | t := New(len(a), len(b)) 34 | a = t.WinA(a) 35 | b = t.WinB(b) 36 | res, e := t.ConvTo(dst, a, b) 37 | if e != nil { 38 | panic(fmt.Sprintf("error %s\n", e)) 39 | } 40 | return res 41 | } 42 | -------------------------------------------------------------------------------- /convol/cmplx/convol_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "math/rand" 10 | "testing" 11 | ) 12 | 13 | func rev(c []complex128) { 14 | n := len(c) 15 | h := n / 2 16 | j := 0 17 | for i := n - 1; i > h; i-- { 18 | c[i], c[j] = c[j], c[i] 19 | j++ 20 | } 21 | } 22 | 23 | func direct(a, b []complex128) []complex128 { 24 | if len(a) < len(b) { 25 | a, b = b, a 26 | } 27 | L := len(a) + len(b) - 1 28 | dst := make([]complex128, L) 29 | var i, j, k int 30 | 31 | for i = 0; i < len(dst); i++ { 32 | ttl := 0i 33 | for j = 0; j < len(a); j++ { 34 | k = i - j 35 | if k < 0 { 36 | continue 37 | } 38 | if k >= len(b) { 39 | continue 40 | } 41 | ttl += a[j] * b[k] 42 | } 43 | dst[i] = ttl 44 | } 45 | return dst 46 | } 47 | 48 | func cmplxApproxEq(a, b []complex128, eps float64) int { 49 | if len(a) != len(b) { 50 | panic(fmt.Sprintf("cannot compare equality of diff length vectors: %d, %d\n", len(a), len(b))) 51 | } 52 | for i := range a { 53 | re, im := real(a[i]), imag(a[i]) 54 | cre, cim := real(b[i]), imag(b[i]) 55 | if math.Abs(re-cre) > eps { 56 | return i 57 | } 58 | if math.Abs(im-cim) > eps { 59 | return i 60 | } 61 | } 62 | return -1 63 | } 64 | 65 | func gen() ([]complex128, []complex128) { 66 | n := rand.Intn(7) + 1 67 | m := rand.Intn(4) + 1 68 | bufA := make([]complex128, m) 69 | bufB := make([]complex128, n) 70 | for i := range bufA { 71 | a := float64(rand.Intn(10)) 72 | b := 0.0 //float64(rand.Intn(10)) 73 | bufA[i] = complex(a, b) 74 | } 75 | for i := range bufB { 76 | a := float64(rand.Intn(10)) 77 | b := 0.0 //float64(rand.Intn(10)) 78 | bufB[i] = complex(a, b) 79 | } 80 | 81 | return bufA, bufB 82 | } 83 | 84 | func TestDirect(t *testing.T) { 85 | for i := 0; i < 1; i++ { 86 | a, b := gen() 87 | d := direct(a, b) 88 | o := To(nil, a, b) 89 | if k := cmplxApproxEq(d, o, 0.001); k != -1 { 90 | t.Errorf("[%d] %v (*) %v @%d: %.2f v %.2f\n", i, a, b, k, d[k], o[k]) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /convol/cmplx/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Package cmplx provides convolution implementations for complex kernels and 5 | // sequences. 6 | package cmplx 7 | -------------------------------------------------------------------------------- /convol/cmplx/k.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import "fmt" 7 | 8 | // K describes a convolver object T which has a precomputed argument component 9 | // ("kernel"). 10 | type K struct { 11 | t *T 12 | kernel []complex128 13 | } 14 | 15 | // Conv computes a convolution of the kernel used 16 | // to construct k with arg, placing the results in 17 | // arg and returning them. Conv returns a non-nil 18 | // error and nil convolution if arg isn't the right 19 | // length. 20 | func (k *K) Conv(arg []complex128) ([]complex128, error) { 21 | if len(arg) != k.t.n { 22 | return nil, fmt.Errorf("arg dimension mismatch, %d != %d", len(arg), k.t.m) 23 | } 24 | arg = k.t.pad(k.t.WinB(arg), k.t.PadL()) 25 | k.t.ft.Do(arg) 26 | for i := range arg { 27 | arg[i] *= k.kernel[i] 28 | } 29 | k.t.ft.Inv(arg) 30 | return arg[:k.t.L()], nil 31 | } 32 | 33 | func (k *K) ConvTo(dst, arg []complex128) ([]complex128, error) { 34 | dst = k.t.WinDst(dst) 35 | copy(dst, arg) 36 | dst = dst[:len(arg)] 37 | return k.Conv(dst) 38 | } 39 | 40 | func (k *K) Win(c []complex128) []complex128 { 41 | return k.t.WinB(c) 42 | } 43 | 44 | func (k *K) M() int { 45 | return k.t.m 46 | } 47 | 48 | func (k *K) N() int { 49 | return k.t.n 50 | } 51 | 52 | func (k *K) L() int { 53 | return k.t.L() 54 | } 55 | 56 | func NewK(kernel []complex128, argLen int) *K { 57 | k, e := New(len(kernel), argLen).K(kernel) 58 | if e != nil { 59 | panic(fmt.Sprintf("%s", e)) 60 | } 61 | return k 62 | } 63 | 64 | func (t *T) K(kernel []complex128) (*K, error) { 65 | if len(kernel) != t.m { 66 | return nil, fmt.Errorf("kernel length wrong %d != %d", len(kernel), t.m) 67 | } 68 | krn := t.WinA(nil) 69 | copy(krn, kernel) 70 | krn = t.pad(krn, t.PadL()) 71 | t.ft.Do(krn) 72 | r := unscale(krn) 73 | for i := range krn { 74 | krn[i] *= r 75 | } 76 | res := &K{ 77 | t: t, 78 | kernel: krn} 79 | return res, nil 80 | } 81 | -------------------------------------------------------------------------------- /convol/cmplx/k_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import "testing" 7 | 8 | func TestK(t *testing.T) { 9 | for i := 0; i < 64; i++ { 10 | a, b := gen() 11 | krn := NewK(a, len(b)) 12 | td := To(nil, a, b) 13 | kd, e := krn.ConvTo(nil, b) 14 | if e != nil { 15 | t.Error(e) 16 | continue 17 | } 18 | if k := cmplxApproxEq(td, kd, 0.001); k != -1 { 19 | t.Errorf("%d kernel/to mismatch %v (*) %v @%d: %.2f %.2f\n", i, a, b, k, td[k], kd[k]) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /convol/cmplx/ola.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | // Ola keeps state for implementing overlap-add 11 | // block convolution 12 | type Ola struct { 13 | k *K 14 | over []complex128 15 | conv []complex128 16 | } 17 | 18 | // NewOla creates a new overlap block convolver 19 | // based on the kernel krn with processing block 20 | // size L. 21 | func NewOla(krn []complex128, L int) *Ola { 22 | k := NewK(krn, L) 23 | return &Ola{ 24 | k: k, 25 | over: make([]complex128, len(krn)-1), 26 | conv: k.Win(nil)} 27 | } 28 | 29 | // M returns the length of the kernel 30 | func (o *Ola) M() int { 31 | return o.k.M() 32 | } 33 | 34 | // N returns the block length of the input 35 | func (o *Ola) N() int { 36 | return o.k.N() 37 | } 38 | 39 | // L returns o.M() + o.N() - 1, the zero 40 | // padding size and size of the underlying fft. 41 | func (o *Ola) L() int { 42 | return o.M() + o.N() - 1 43 | } 44 | 45 | // WinSrc takes a candidate window slice c and returns a slice properly 46 | // proportioned, in terms of both length and capacity for passing as src arg of 47 | // Block(). 48 | // 49 | // The returned slice uses the backing store of c if possible and contains the 50 | // elements of c. 51 | func (o *Ola) WinSrc(c []complex128) []complex128 { 52 | return o.k.Win(c) 53 | } 54 | 55 | // WinDst takes a candidate window slice c and returns a slice properly 56 | // proportioned, in terms of both length and capacity, for passing as arg dst 57 | // to Block(). 58 | // 59 | // The returned slice uses the backing store of c if possible and contains the 60 | // elements of c. 61 | func (o *Ola) WinDst(c []complex128) []complex128 { 62 | return o.k.t.ft.Win(c)[:o.k.t.n] 63 | } 64 | 65 | // Block processes one block of the convolution 66 | func (o *Ola) Block(src, dst []complex128) error { 67 | conv, e := o.k.ConvTo(o.conv, src) 68 | if e != nil { 69 | return e 70 | } 71 | o.conv = conv 72 | M := o.k.t.m - 1 73 | fmt.Printf("M %d, len(dst) %d len(over) %d len(conv) %d\n", M, len(dst), len(o.over), len(conv)) 74 | for i := 0; i < M; i++ { 75 | dst[i] = o.over[i] + conv[i] 76 | } 77 | copy(o.over, conv[o.N():]) 78 | copy(dst[M:], conv[M:o.N()]) 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /convol/cmplx/ola_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | "testing" 10 | ) 11 | 12 | func genLong() []complex128 { 13 | n := rand.Intn(128) + 128 14 | res := make([]complex128, n) 15 | for i := range res { 16 | a := float64(rand.Intn(10)) 17 | b := float64(rand.Intn(10)) 18 | res[i] = complex(a, b) 19 | } 20 | return res 21 | } 22 | 23 | func TestOla(t *testing.T) { 24 | // we just compare ola to k on random inputs. 25 | for i := 0; i < 64; i++ { 26 | krn, _ := gen() 27 | seq := genLong() 28 | blk := len(seq) / len(krn) 29 | if blk < 2*len(krn) { 30 | fmt.Printf("skipping %d, seq not long enough", i) 31 | } 32 | ola := NewOla(krn, blk) 33 | k := NewK(krn, len(seq)) 34 | kRes, e := k.ConvTo(nil, seq) 35 | if e != nil { 36 | t.Error(e) 37 | continue 38 | } 39 | kRes = kRes[:len(seq)] 40 | blkWin := ola.WinSrc(nil) 41 | dstWin := ola.WinDst(nil) 42 | n := 0 43 | for i := 0; i < len(krn)-1; i++ { 44 | seq = append(seq, 0i) 45 | } 46 | //fmt.Printf("ola on seq %d with kernel %d, kRes len is %d\n", len(seq), len(krn), len(kRes)) 47 | for n < len(kRes) { 48 | end := n + ola.N() 49 | copy(blkWin, seq[n:end]) 50 | if e := ola.Block(blkWin, dstWin); e != nil { 51 | t.Fatalf("%d ola error: %s\n", i, e) 52 | } 53 | resEnd := end 54 | if resEnd > len(kRes) { 55 | resEnd = len(kRes) 56 | } 57 | if k := cmplxApproxEq(kRes[n:resEnd], dstWin[:resEnd-n], 0.001); k != -1 { 58 | t.Errorf("ola run %d: data error at %d. %.2f v %.2f", i, n+k, kRes[n+k], dstWin[k]) 59 | } 60 | n += resEnd - n 61 | //fmt.Printf("\tprocessed %d\n", n) 62 | } 63 | } 64 | } 65 | 66 | func TestOlaSource(t *testing.T) { 67 | } 68 | -------------------------------------------------------------------------------- /convol/cmplx/t.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/zikichombo/dsp/fft" 10 | ) 11 | 12 | // T holds state for performing n by m sized linear convolution. 13 | type T struct { 14 | n, m int 15 | winB []complex128 16 | ft *fft.T 17 | } 18 | 19 | // New creates a new convolver object for repeatedly performing 20 | // linear convolution of two arguments of length m, n. 21 | func New(m, n int) *T { 22 | L := n + m - 1 23 | res := &T{ 24 | n: n, 25 | m: m} 26 | res.ft = fft.New(res.PadL()) 27 | res.winB = res.ft.Win(nil)[:L] 28 | return res 29 | } 30 | 31 | // M returns the length of the first argument. 32 | func (t *T) M() int { 33 | return t.m 34 | } 35 | 36 | // N returns the length of the second argument. 37 | func (t *T) N() int { 38 | return t.n 39 | } 40 | 41 | // L returns the length of the result, which is 42 | // 43 | // t.N() + t.M() - 1 44 | func (t *T) L() int { 45 | return t.n + t.m - 1 46 | } 47 | 48 | // PadL returns the fft padded length (can be 49 | // > L). 50 | func (t *T) PadL() int { 51 | L := t.L() 52 | if L&(L-1) == 0 { 53 | return L 54 | } 55 | res := 1 56 | for res < L { 57 | res *= 2 58 | } 59 | return res 60 | } 61 | 62 | // Conv performs a linear convolution of a and b, placing 63 | // the results in a and returning them. 64 | // 65 | // Conv returns a non-nil error if the lengths of a and b 66 | // do not conform to t.N() and t.M(). 67 | // 68 | // Upon return, len(a) = t.L(), which is larger than 69 | // t.N(). To avoid a copy, a can be created by t.WinDst(nil). 70 | // 71 | // a := t.WinDst(nil) 72 | // b := ... 73 | // var err error 74 | // a, err = t.Conv(a, b) 75 | func (t *T) Conv(a, b []complex128) ([]complex128, error) { 76 | if len(a) != t.m { 77 | return nil, fmt.Errorf("operand dimension mismatch: %d != %d", len(a), t.m) 78 | } 79 | if len(b) != t.n { 80 | return nil, fmt.Errorf("kernel dimension mismatch: %d != %d", len(b), t.n) 81 | } 82 | copy(t.winB, b) 83 | return t.conv(a, t.winB[:len(b)]) 84 | } 85 | 86 | // clobbers b with ifft. 87 | func (t *T) conv(a, b []complex128) ([]complex128, error) { 88 | L := t.PadL() 89 | a = t.pad(a, L) 90 | b = t.pad(b, L) 91 | fmt.Printf("b %v\n", b) 92 | if e := t.ft.Do(b); e != nil { 93 | return nil, e 94 | } 95 | t.ft.Scale(false) 96 | if e := t.ft.Do(a); e != nil { 97 | return nil, e 98 | } 99 | t.ft.Scale(true) 100 | for i := range a { 101 | fmt.Printf("a %f b %f\n", a[i], b[i]) 102 | a[i] *= b[i] 103 | } 104 | if e := t.ft.Inv(a); e != nil { 105 | return nil, e 106 | } 107 | return a[:t.L()], nil 108 | } 109 | 110 | // ConvTo performs a linear convolution of a and b, 111 | // placing the result in dst. If dst is does not have sufficient capacity, an 112 | // appropriately len- and cap- dimensioned slice 113 | // is allocated and returned in its place. 114 | func (t *T) ConvTo(dst, a, b []complex128) ([]complex128, error) { 115 | dst = t.WinDst(dst) 116 | copy(dst, a) 117 | return t.Conv(dst[:t.m], t.WinB(b)) 118 | } 119 | 120 | // WinA returns a slice with len and cap dimensions 121 | // set so that if the returned slice a is passed 122 | // as the first argument to Conv(), then no 123 | // a-argument related copying or allocations take place during the 124 | // execution of Conv. 125 | func (t *T) WinA(c []complex128) []complex128 { 126 | return t.ft.Win(c)[:t.m] 127 | } 128 | 129 | // WinB returns a slice with len and cap dimensions 130 | // set so that if the returned slice b is passed 131 | // as the second argument to Conv(), then no 132 | // b-argument related copying or allocations take place during the 133 | // execution of Conv. 134 | func (t *T) WinB(c []complex128) []complex128 { 135 | return t.ft.Win(c)[:t.n] 136 | } 137 | 138 | // WinDst returns a slice with len and cap dimensions 139 | // set so that if the returned slice is passed as 140 | // the dst argument to Conv, then no dst related 141 | // allocations or copying takes place during the 142 | // execution of Conv() 143 | func (t *T) WinDst(c []complex128) []complex128 { 144 | return t.ft.Win(c)[:t.L()] 145 | } 146 | 147 | func (t *T) pad(sl []complex128, L int) []complex128 { 148 | n := len(sl) 149 | if cap(sl) < L { 150 | tmp := make([]complex128, n, L) 151 | copy(tmp, sl) 152 | sl = tmp 153 | } 154 | sl = sl[:L] 155 | for i := n; i < L; i++ { 156 | sl[i] = 0i 157 | } 158 | return sl 159 | } 160 | -------------------------------------------------------------------------------- /convol/cmplx/t_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import "testing" 7 | 8 | func TestT(t *testing.T) { 9 | } 10 | -------------------------------------------------------------------------------- /convol/cmplx/unscale.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package cmplx 5 | 6 | import "math" 7 | 8 | func unscale(d []complex128) complex128 { 9 | n := float64(len(d)) 10 | r := math.Sqrt(n) 11 | return complex(r, 0) 12 | } 13 | -------------------------------------------------------------------------------- /convol/convol.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import "fmt" 7 | 8 | // Do performs linear convolution of a and b, placing 9 | // the result in a and returning it if there is space, 10 | // otherwise a new slice is allocated. 11 | // 12 | // To avoid the allocation, it should be that 13 | // 14 | // cap(a) >= len(a) + len(b) - 1 15 | // 16 | func Do(a, b []float64) []float64 { 17 | t := New(len(a), len(b)) 18 | a = t.WinA(a) 19 | b = t.WinB(b) 20 | res, e := t.Conv(a, b) 21 | if e != nil { 22 | panic(fmt.Sprintf("error %s\n", e)) 23 | } 24 | return res 25 | } 26 | 27 | // To performs linear convolution of a and b, placing 28 | // the result in dst and returning it. 29 | // 30 | // if dst does not have sufficient capacity, a new slice 31 | // is allocated and returned in its place. 32 | func To(dst, a, b []float64) []float64 { 33 | t := New(len(a), len(b)) 34 | a = t.WinA(a) 35 | b = t.WinB(b) 36 | res, e := t.ConvTo(dst, a, b) 37 | if e != nil { 38 | panic(fmt.Sprintf("error %s\n", e)) 39 | } 40 | return res 41 | } 42 | -------------------------------------------------------------------------------- /convol/convol_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "math/rand" 10 | "testing" 11 | ) 12 | 13 | func rev(c []float64) { 14 | n := len(c) 15 | h := n / 2 16 | j := 0 17 | for i := n - 1; i > h; i-- { 18 | c[i], c[j] = c[j], c[i] 19 | j++ 20 | } 21 | } 22 | 23 | func direct(a, b []float64) []float64 { 24 | if len(a) < len(b) { 25 | a, b = b, a 26 | } 27 | L := len(a) + len(b) - 1 28 | dst := make([]float64, L) 29 | var i, j, k int 30 | 31 | for i = 0; i < len(dst); i++ { 32 | ttl := 0.0 33 | for j = 0; j < len(a); j++ { 34 | k = i - j 35 | if k < 0 { 36 | continue 37 | } 38 | if k >= len(b) { 39 | continue 40 | } 41 | ttl += a[j] * b[k] 42 | } 43 | dst[i] = ttl 44 | } 45 | return dst 46 | } 47 | 48 | func approxEq(a, b []float64, eps float64) int { 49 | if len(a) != len(b) { 50 | panic(fmt.Sprintf("cannot compare equality of diff length vectors: %d, %d\n", len(a), len(b))) 51 | } 52 | for i := range a { 53 | if math.Abs(a[i]-b[i]) > eps { 54 | return i 55 | } 56 | } 57 | return -1 58 | } 59 | 60 | func gen() ([]float64, []float64) { 61 | n := rand.Intn(7) + 1 62 | m := rand.Intn(4) + 1 63 | if n%2 == 1 { 64 | n++ 65 | } 66 | if m%2 == 1 { 67 | m++ 68 | } 69 | bufA := make([]float64, m) 70 | bufB := make([]float64, n) 71 | for i := range bufA { 72 | a := float64(rand.Intn(10)) 73 | bufA[i] = a 74 | } 75 | for i := range bufB { 76 | b := float64(rand.Intn(10)) 77 | bufB[i] = b 78 | } 79 | 80 | return bufA, bufB 81 | } 82 | 83 | func TestDirect(t *testing.T) { 84 | for i := 0; i < 64; i++ { 85 | a, b := gen() 86 | d := direct(a, b) 87 | o := To(nil, a, b) 88 | if k := approxEq(d, o, 0.001); k != -1 { 89 | t.Errorf("[%d] %v (*) %v @%d: %.2f v %.2f\n", i, a, b, k, d[k], o[k]) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /convol/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Package convol provides convolution implementations. 5 | // 6 | package convol 7 | -------------------------------------------------------------------------------- /convol/k.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/zikichombo/dsp/fft" 10 | ) 11 | 12 | // K describes a convolver object T which has a precomputed argument component 13 | // ("kernel"). 14 | type K struct { 15 | t *T 16 | kernel fft.HalfComplex 17 | } 18 | 19 | // Conv computes a convolution of the kernel used 20 | // to construct k with arg, placing the results in 21 | // arg and returning them. Conv returns a non-nil 22 | // error and nil convolution if arg isn't the right 23 | // length. 24 | func (k *K) Conv(arg []float64) ([]float64, error) { 25 | if len(arg) != k.t.n { 26 | return nil, fmt.Errorf("arg dimension mismatch, %d != %d", len(arg), k.t.m) 27 | } 28 | arg = k.t.pad(k.t.WinB(arg), k.t.PadL()) 29 | hc := k.t.ft.Do(arg) 30 | hc.MulElems(k.kernel) 31 | arg = k.t.ft.Inv(hc) 32 | return arg[:k.t.L()], nil 33 | } 34 | 35 | // ConvTo computes convolution of the kernel 36 | func (k *K) ConvTo(dst, arg []float64) ([]float64, error) { 37 | dst = k.t.WinDst(dst) 38 | copy(dst, arg) 39 | dst = dst[:len(arg)] 40 | return k.Conv(dst) 41 | } 42 | 43 | // Win returns a slice containing everything in c with 44 | // length and capacity set so that no copying 45 | // copying takes place if c is used to house argument data 46 | // 47 | // c := k.Win(nil) 48 | // for i := range c { 49 | // c[i] = ... 50 | // } 51 | // k.Conv(c) // no copying 52 | func (k *K) Win(c []float64) []float64 { 53 | return k.t.WinB(c) 54 | } 55 | 56 | // M() returns the length of the kernel. 57 | func (k *K) M() int { 58 | return k.t.m 59 | } 60 | 61 | // N() returns the length of the argument. 62 | func (k *K) N() int { 63 | return k.t.n 64 | } 65 | 66 | // L() returns the length of the result, which is 67 | // 68 | // M() + N() - 1 69 | // 70 | func (k *K) L() int { 71 | return k.t.L() 72 | } 73 | 74 | // NewK creates a new convolver using "kernel" as 75 | // the kernel. 76 | func NewK(kernel []float64, argLen int) *K { 77 | k, e := New(len(kernel), argLen).K(kernel) 78 | if e != nil { 79 | panic(e) 80 | } 81 | return k 82 | } 83 | 84 | // K() creates a new kernel-convolver with using "kernel" 85 | // as the kernel. 86 | func (t *T) K(kernel []float64) (*K, error) { 87 | if len(kernel) != t.m { 88 | return nil, fmt.Errorf("kernel length wrong %d != %d", len(kernel), t.m) 89 | } 90 | krn := t.WinA(nil) 91 | copy(krn, kernel) 92 | krn = t.pad(krn, t.PadL()) 93 | t.ft.Scale(false) 94 | hc := t.ft.Do(krn) 95 | t.ft.Scale(true) 96 | res := &K{ 97 | t: t, 98 | kernel: hc} 99 | return res, nil 100 | } 101 | -------------------------------------------------------------------------------- /convol/k_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import "testing" 7 | 8 | func TestK(t *testing.T) { 9 | for i := 0; i < 64; i++ { 10 | a, b := gen() 11 | krn := NewK(a, len(b)) 12 | td := To(nil, a, b) 13 | kd, e := krn.ConvTo(nil, b) 14 | if e != nil { 15 | t.Error(e) 16 | continue 17 | } 18 | if k := approxEq(td, kd, 0.001); k != -1 { 19 | t.Errorf("%d kernel/to mismatch %v (*) %v @%d: %.2f %.2f\n", i, a, b, k, td[k], kd[k]) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /convol/ola.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | // Ola keeps state for implementing overlap-add 7 | // block convolution 8 | type Ola struct { 9 | k *K 10 | over []float64 11 | conv []float64 12 | } 13 | 14 | // NewOla creates a new overlap block convolver 15 | // based on the kernel krn with processing block 16 | // size L. 17 | func NewOla(krn []float64, L int) *Ola { 18 | k := NewK(krn, L) 19 | return &Ola{ 20 | k: k, 21 | over: make([]float64, len(krn)-1), 22 | conv: k.Win(nil)} 23 | } 24 | 25 | // M returns the length of the kernel 26 | func (o *Ola) M() int { 27 | return o.k.M() 28 | } 29 | 30 | // N returns the block length of the input 31 | func (o *Ola) N() int { 32 | return o.k.N() 33 | } 34 | 35 | // L returns o.M() + o.N() - 1, the zero 36 | // padding size and size of the underlying fft. 37 | func (o *Ola) L() int { 38 | return o.M() + o.N() - 1 39 | } 40 | 41 | // WinSrc takes a candidate window slice c and returns a slice properly 42 | // proportioned, in terms of both length and capacity for passing as src arg of 43 | // Block(). 44 | // 45 | // The returned slice uses the backing store of c if possible and contains the 46 | // elements of c. 47 | func (o *Ola) WinSrc(c []float64) []float64 { 48 | return o.k.Win(c) 49 | } 50 | 51 | // WinDst takes a candidate window slice c and returns a slice properly 52 | // proportioned, in terms of both length and capacity, for passing as arg dst 53 | // to Block(). 54 | // 55 | // The returned slice uses the backing store of c if possible and contains the 56 | // elements of c. 57 | func (o *Ola) WinDst(c []float64) []float64 { 58 | return o.k.t.win(c, o.k.t.n) 59 | } 60 | 61 | // Block processes one block of the convolution 62 | func (o *Ola) Block(src, dst []float64) error { 63 | conv, e := o.k.ConvTo(o.conv, src) 64 | if e != nil { 65 | return e 66 | } 67 | o.conv = conv 68 | M := o.k.t.m - 1 69 | for i := 0; i < M; i++ { 70 | dst[i] = o.over[i] + conv[i] 71 | } 72 | copy(o.over, conv[o.N():]) 73 | copy(dst[M:], conv[M:o.N()]) 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /convol/ola_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | "testing" 10 | ) 11 | 12 | func genLong() []float64 { 13 | n := rand.Intn(128) + 128 14 | res := make([]float64, n) 15 | for i := range res { 16 | a := float64(rand.Intn(10)) 17 | res[i] = a 18 | } 19 | return res 20 | } 21 | 22 | func TestOla(t *testing.T) { 23 | // we just compare ola to k on random inputs. 24 | for i := 0; i < 64; i++ { 25 | krn, _ := gen() 26 | seq := genLong() 27 | blk := len(seq) / len(krn) 28 | if blk < 2*len(krn) { 29 | fmt.Printf("skipping %d, seq not long enough", i) 30 | } 31 | ola := NewOla(krn, blk) 32 | k := NewK(krn, len(seq)) 33 | kRes, e := k.ConvTo(nil, seq) 34 | if e != nil { 35 | t.Error(e) 36 | continue 37 | } 38 | kRes = kRes[:len(seq)] 39 | blkWin := ola.WinSrc(nil) 40 | dstWin := ola.WinDst(nil) 41 | n := 0 42 | for i := 0; i < len(krn)-1; i++ { 43 | seq = append(seq, 0i) 44 | } 45 | //fmt.Printf("ola on seq %d with kernel %d, kRes len is %d\n", len(seq), len(krn), len(kRes)) 46 | for n < len(kRes) { 47 | end := n + ola.N() 48 | copy(blkWin, seq[n:end]) 49 | if e := ola.Block(blkWin, dstWin); e != nil { 50 | t.Fatalf("%d ola error: %s\n", i, e) 51 | } 52 | resEnd := end 53 | if resEnd > len(kRes) { 54 | resEnd = len(kRes) 55 | } 56 | if k := approxEq(kRes[n:resEnd], dstWin[:resEnd-n], 0.001); k != -1 { 57 | t.Errorf("ola run %d: data error at %d. %.2f v %.2f", i, n+k, kRes[n+k], dstWin[k]) 58 | } 59 | n += resEnd - n 60 | //fmt.Printf("\tprocessed %d\n", n) 61 | } 62 | } 63 | } 64 | 65 | func TestOlaSource(t *testing.T) { 66 | } 67 | -------------------------------------------------------------------------------- /convol/t.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/zikichombo/dsp/fft" 10 | ) 11 | 12 | // T holds state for performing n by m sized linear convolution. 13 | type T struct { 14 | n, m int 15 | winB []float64 16 | ft *fft.Real 17 | } 18 | 19 | // New creates a new convolver object for repeatedly performing 20 | // linear convolution of two arguments of length m, n. 21 | func New(m, n int) *T { 22 | L := n + m - 1 23 | res := &T{ 24 | n: n, 25 | m: m} 26 | res.ft = fft.NewReal(res.PadL()) 27 | res.winB = make([]float64, res.ft.N())[:L] 28 | return res 29 | } 30 | 31 | // M returns the length of the first argument 32 | func (t *T) M() int { 33 | return t.m 34 | } 35 | 36 | // N returns the length of the second argument. 37 | func (t *T) N() int { 38 | return t.n 39 | } 40 | 41 | // L returns the length of the result, which is 42 | // 43 | // t.N() + t.M() - 1 44 | func (t *T) L() int { 45 | return t.n + t.m - 1 46 | } 47 | 48 | // PadL returns the fft padded length (can be 49 | // > L). 50 | func (t *T) PadL() int { 51 | L := t.L() 52 | if L&(L-1) == 0 { 53 | return L 54 | } 55 | res := 1 56 | for res < L { 57 | res *= 2 58 | } 59 | return res 60 | } 61 | 62 | // Conv performs a linear convolution of a and b, placing 63 | // the results in a and returning them. 64 | // 65 | // Conv returns a non-nil error if the lengths of a and b 66 | // do not conform to t.N() and t.M(). 67 | // 68 | // Upon return, len(a) = t.L(), which is larger than 69 | // t.N(). To avoid a copy, a can be created by t.WinDst(nil). 70 | // 71 | // a := t.WinDst(nil) 72 | // b := ... 73 | // var err error 74 | // a, err = t.Conv(a, b) 75 | func (t *T) Conv(a, b []float64) ([]float64, error) { 76 | if len(a) != t.m { 77 | return nil, fmt.Errorf("operand dimension mismatch: %d != %d", len(a), t.m) 78 | } 79 | if len(b) != t.n { 80 | return nil, fmt.Errorf("kernel dimension mismatch: %d != %d", len(b), t.n) 81 | } 82 | copy(t.winB, b) 83 | return t.conv(a, t.winB[:len(b)]) 84 | } 85 | 86 | // clobbers b with ifft. 87 | func (t *T) conv(a, b []float64) ([]float64, error) { 88 | L := t.PadL() 89 | a = t.pad(a, L) 90 | b = t.pad(b, L) 91 | hcb := t.ft.Do(b) 92 | t.ft.Scale(false) 93 | hca := t.ft.Do(a) 94 | t.ft.Scale(true) 95 | hca = hca.MulElems(hcb) 96 | a = t.ft.Inv(hca) 97 | return a[:t.L()], nil 98 | } 99 | 100 | // ConvTo performs a linear convolution of a and b, 101 | // placing the result in dst. If dst is does not have sufficient capacity, an 102 | // appropriately len- and cap- dimensioned slice 103 | // is allocated and returned in its place. 104 | func (t *T) ConvTo(dst, a, b []float64) ([]float64, error) { 105 | dst = t.WinDst(dst) 106 | copy(dst, a) 107 | return t.Conv(dst[:t.m], t.WinB(b)) 108 | } 109 | 110 | // WinA returns a slice with len and cap dimensions 111 | // set so that if the returned slice a is passed 112 | // as the first argument to Conv(), then no 113 | // a-argument related copying or allocations take place during the 114 | // execution of Conv. 115 | func (t *T) WinA(d []float64) []float64 { 116 | return t.win(d, t.m) 117 | } 118 | 119 | // WinB returns a slice with len and cap dimensions 120 | // set so that if the returned slice b is passed 121 | // as the second argument to Conv(), then no 122 | // b-argument related copying or allocations take place during the 123 | // execution of Conv. 124 | func (t *T) WinB(d []float64) []float64 { 125 | return t.win(d, t.n) 126 | } 127 | 128 | // WinDst returns a slice with len and cap dimensions 129 | // set so that if the returned slice is passed as 130 | // the dst argument to Conv, then no dst related 131 | // allocations or copying takes place during the 132 | // execution of Conv() 133 | func (t *T) WinDst(d []float64) []float64 { 134 | return t.win(d, t.L()) 135 | } 136 | 137 | func (t *T) win(d []float64, trgLen int) []float64 { 138 | m := len(d) 139 | if cap(d) < t.ft.N() { 140 | tmp := make([]float64, t.ft.N()) 141 | copy(tmp, d) 142 | d = tmp 143 | } 144 | d = d[:cap(d)] 145 | for i := m; i < len(d); i++ { 146 | d[i] = 0.0 147 | } 148 | return d[:trgLen] 149 | } 150 | 151 | func (t *T) pad(sl []float64, L int) []float64 { 152 | n := len(sl) 153 | if cap(sl) < L { 154 | tmp := make([]float64, n, L) 155 | copy(tmp, sl) 156 | sl = tmp 157 | } 158 | sl = sl[:L] 159 | for i := n; i < L; i++ { 160 | sl[i] = 0.0 161 | } 162 | return sl 163 | } 164 | -------------------------------------------------------------------------------- /convol/t_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import "testing" 7 | 8 | func TestT(t *testing.T) { 9 | } 10 | -------------------------------------------------------------------------------- /convol/unscale.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package convol 5 | 6 | import "math" 7 | 8 | func unscale(d []float64) float64 { 9 | n := float64(len(d)) 10 | r := math.Sqrt(n) 11 | return r 12 | } 13 | -------------------------------------------------------------------------------- /czt/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Package czt implements the chirp-z transform. 5 | // 6 | // From "Chirp Z Transform, Laurence R Rabiner, Ronald W. Schafer, Charles M. Rader 7 | // Nov 21 1968, Bell System Technical Journal, May-June 1969 8 | // 9 | // Package czt is part of http://zikichombo.org 10 | package czt 11 | -------------------------------------------------------------------------------- /czt/t.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package czt 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "math/cmplx" 10 | 11 | "github.com/zikichombo/dsp/fft" 12 | "github.com/zikichombo/sound/freq" 13 | ) 14 | 15 | type T struct { 16 | nS, nB, nPad int 17 | start, step float64 18 | kern, aTab, wTab []complex128 19 | ft *fft.T 20 | } 21 | 22 | // New creates a new chirp-z transformer object. 23 | // 24 | // The transformer takes as input nS samples and 25 | // returns a freqrency domain picture of those 26 | // samples with nB bins, focusing on frequencies 27 | // from start to end. Start and end are in radians 28 | // per sample. 29 | func New(nS, nB int, start, end float64) *T { 30 | nPad := findL(nS, nB) 31 | step := (end - start) / float64(nB) 32 | res := &T{ 33 | nS: nS, 34 | nB: nB, 35 | nPad: nPad, 36 | start: start, 37 | step: step, 38 | ft: fft.New(nPad), 39 | kern: make([]complex128, nPad), 40 | aTab: make([]complex128, nS), 41 | wTab: make([]complex128, nS)} 42 | res.initWK() 43 | res.initA() 44 | return res 45 | } 46 | 47 | // Do performs the transform as configured in 48 | // the call to New() which created t. 49 | func (t *T) Do(src []complex128) []complex128 { 50 | src = t.Win(src) 51 | src = pad(src, t.nPad) 52 | 53 | // weight inputs 54 | for i := range src[:t.nS] { 55 | src[i] *= t.aTab[i] * t.wTab[i] 56 | } 57 | 58 | // re scaling: kernel is scaled, so we don't scale here 59 | // and scaling factor of kernel is incorporated in m 60 | // multiplication below. 61 | t.ft.Scale(false) 62 | e := t.ft.Do(src) 63 | t.ft.Scale(true) 64 | if e != nil { 65 | panic(fmt.Sprintf("%s", e)) 66 | } 67 | 68 | for i := 0; i < t.nPad; i++ { 69 | src[i] *= t.kern[i] 70 | } 71 | 72 | t.ft.Inv(src) 73 | 74 | // output step 75 | r := complex(1/math.Sqrt(float64(t.nS)), 0) 76 | for i := 0; i < t.nB; i++ { 77 | src[i] *= t.wTab[i] * r 78 | } 79 | return src[:t.nB] 80 | } 81 | 82 | // NB returns the number of frequency bins produced by the transform 83 | func (t *T) NB() int { 84 | return t.nB 85 | } 86 | 87 | // NS returns the number of samples expected by the transform. 88 | func (t *T) NS() int { 89 | return t.nS 90 | } 91 | 92 | // PadN returns the underlying fft pad size. 93 | func (t *T) PadN() int { 94 | return t.nPad 95 | } 96 | 97 | // FreqRange returns the range of frequencies for which 98 | // coefficients are computed. 99 | func (t *T) FreqRange(sf freq.T) (l freq.T, u freq.T) { 100 | l = sf.FreqOf(t.start) 101 | d := sf.FreqOf(t.step * float64(t.nB)) 102 | return l, l + d 103 | } 104 | 105 | // FreqStep returns the difference between the 106 | // center frequencies of any two adjacent frequency bins. 107 | func (t *T) FreqStep(sf freq.T) freq.T { 108 | l, u := t.FreqRange(sf) 109 | return (u - l) / freq.T(t.nB) 110 | } 111 | 112 | // BinRange returns the frequency range of a given frequency bin 113 | // for which a coefficient can be computed. 114 | func (t *T) BinRange(sf freq.T, i int) (l freq.T, u freq.T) { 115 | L, _ := t.FreqRange(sf) 116 | step := t.FreqStep(sf) 117 | return L + step*freq.T(i), L + step*freq.T(i+1) 118 | } 119 | 120 | // Win returns a window with appropriate capacity and length 121 | // with the contents of c. 122 | func (t *T) Win(c []complex128) []complex128 { 123 | if cap(c) < t.nPad { 124 | tmp := make([]complex128, t.nPad) 125 | copy(tmp, c) 126 | c = tmp 127 | } 128 | return c[:t.nS] 129 | } 130 | 131 | func (t *T) initA() { 132 | for i := 0; i < t.nS; i++ { 133 | t.aTab[i] = cmplx.Exp(complex(0, -float64(i)*t.start)) 134 | } 135 | } 136 | 137 | func (t *T) initWK() { 138 | for i := 0; i < t.nS; i++ { 139 | c := cmplx.Exp(complex(0, t.step*(float64(i*i)/2))) 140 | t.kern[i] = c 141 | t.wTab[i] = cmplx.Conj(c) 142 | } 143 | // wrap it for special properties of kernel def of convolution 144 | // (k[-n] = k[n] unlike linear convolution) 145 | for i := 1; i < t.nS; i++ { 146 | t.kern[t.nPad-i] = t.kern[i] 147 | } 148 | // nb: scaled. 149 | t.ft.Do(t.kern) 150 | } 151 | 152 | func pad(d []complex128, n int) []complex128 { 153 | m := len(d) 154 | d = d[:n] 155 | for i := m; i < n; i++ { 156 | d[i] = 0i 157 | } 158 | return d 159 | } 160 | 161 | func findL(nS, nB int) int { 162 | //L := nS + nB - 1 163 | L := 2*nS - 1 164 | res := 1 165 | for res < L { 166 | res *= 2 167 | } 168 | return res 169 | } 170 | -------------------------------------------------------------------------------- /czt/t_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package czt 5 | 6 | import ( 7 | "math" 8 | "math/cmplx" 9 | "math/rand" 10 | "testing" 11 | 12 | "github.com/zikichombo/dsp/fft" 13 | "github.com/zikichombo/sound" 14 | "github.com/zikichombo/sound/freq" 15 | "github.com/zikichombo/sound/gen" 16 | "github.com/zikichombo/sound/ops" 17 | ) 18 | 19 | func TestCztFftEq(t *testing.T) { 20 | L := 2 21 | U := 129 22 | N := 128 23 | for i := 0; i < N; i++ { 24 | d := genCmplx(L, U) 25 | czt := New(len(d), len(d), 0, 2*math.Pi) 26 | ft := fft.New(len(d)) 27 | d = czt.Win(d) 28 | e := ft.Win(nil) 29 | 30 | copy(e, d) 31 | czt.Do(d) 32 | ft.Do(e) 33 | if b := cmplxApproxEq(d, e, 0.0001); b != -1 { 34 | t.Errorf("bs run %d, sz %d bin %d, %.3f v %.3f\n", i, len(d), b, d[b], e[b]) 35 | } 36 | } 37 | } 38 | 39 | func TestCztFftZoomStartEq(t *testing.T) { 40 | SF := 1000 * freq.Hertz 41 | f1 := 53 * freq.Hertz 42 | f2 := f1 + 4*freq.Hertz 43 | sins, _ := ops.Add([]sound.Source{ 44 | gen.Sin(f1), 45 | gen.Sin(f2)}...) 46 | //sins = gen.Constant(1.0) 47 | M := 10 48 | N := M 49 | L := 0 * freq.Hertz 50 | U := 100 * freq.Hertz 51 | for i := 1; i < 8; i++ { 52 | //fmt.Printf("zoom %d\n", i) 53 | ft := fft.New(N) 54 | ftw := ft.Win(nil) 55 | ops.SlurpCmplx(sins, ftw) 56 | ct := New(N, N/M, SF.RadsPer(L), SF.RadsPer(U)) 57 | ctw := ct.Win(nil) 58 | copy(ctw, ftw) 59 | ft.Do(ftw) 60 | ct.Do(ctw) 61 | d := N / M 62 | k := 0 63 | for k < d { 64 | if !cmplxClose(ftw[k], ctw[k], 0.0001) { 65 | fl, fu := fft.BinRange(SF, N, k) 66 | cl, cu := ct.BinRange(SF, k) 67 | fm, fp := cmplx.Polar(ftw[k]) 68 | cm, cp := cmplx.Polar(ctw[k]) 69 | t.Errorf("[%s..%s] fft |%.3f| <%.3f> %.2f [%s..%s] czt |%.3f| <%.3f> %.2f\n", fl, fu, fm, fp, ftw[k], cl, cu, cm, cp, ctw[k]) 70 | } 71 | k++ 72 | } 73 | N *= 2 74 | } 75 | } 76 | 77 | func TestCztFftZoomMidEq(t *testing.T) { 78 | SF := 1000 * freq.Hertz 79 | f1 := 253 * freq.Hertz 80 | f2 := f1 + 4*freq.Hertz 81 | sins, _ := ops.Add([]sound.Source{ 82 | gen.Sin(f1), 83 | gen.Sin(f2)}...) 84 | //sins = gen.Constant(1.0) 85 | M := 10 86 | N := M 87 | L := 200 * freq.Hertz 88 | U := L + 100*freq.Hertz 89 | for i := 1; i < 8; i++ { 90 | //fmt.Printf("zoom %d\n", i) 91 | ft := fft.New(N) 92 | ftw := ft.Win(nil) 93 | ops.SlurpCmplx(sins, ftw) 94 | ct := New(N, N/M, SF.RadsPer(L), SF.RadsPer(U)) 95 | ctw := ct.Win(nil) 96 | copy(ctw, ftw) 97 | ft.Do(ftw) 98 | ct.Do(ctw) 99 | d := N / M 100 | j := N / 5 101 | k := 0 102 | for k < d { 103 | if !cmplxClose(ftw[j], ctw[k], 0.00001) { 104 | fl, fu := fft.BinRange(SF, N, j) 105 | cl, cu := ct.BinRange(SF, k) 106 | fm, fp := cmplx.Polar(ftw[j]) 107 | cm, cp := cmplx.Polar(ctw[k]) 108 | t.Errorf("[%s..%s] fft |%.3f| <%.3f> %.2f [%s..%s] czt |%.3f| <%.3f> %.2f\n", fl, fu, fm, fp, ftw[j], cl, cu, cm, cp, ctw[k]) 109 | } 110 | j++ 111 | k++ 112 | } 113 | N *= 2 114 | } 115 | } 116 | 117 | func genCmplx(l, u int) []complex128 { 118 | r := rand.Intn(u - l) 119 | s := l + r 120 | res := make([]complex128, s) 121 | for i := range res { 122 | res[i] = complex(rand.Float64(), rand.Float64()) 123 | } 124 | return res 125 | } 126 | 127 | func cmplxClose(a, b complex128, eps float64) bool { 128 | ra, ia := real(a), imag(a) 129 | rb, ib := real(b), imag(b) 130 | return math.Abs(ra-rb) < eps && math.Abs(ia-ib) < eps 131 | } 132 | 133 | func cmplxApproxEq(a, b []complex128, eps float64) int { 134 | for i := range a { 135 | ca := a[i] 136 | cb := b[i] 137 | ra := real(ca) 138 | rb := real(cb) 139 | ia := imag(ca) 140 | ib := imag(cb) 141 | if math.Abs(ra-rb) >= eps { 142 | return i 143 | } 144 | if math.Abs(ia-ib) >= eps { 145 | return i 146 | } 147 | } 148 | return -1 149 | } 150 | -------------------------------------------------------------------------------- /dct/cos.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package dct 5 | 6 | import ( 7 | "math" 8 | ) 9 | 10 | const cosTblBits = 10 11 | 12 | var cosTbl [][]float64 13 | 14 | func init() { 15 | cosTbl = make([][]float64, cosTblBits+1) 16 | i := uint(0) 17 | var n int 18 | for { 19 | n = 1 << i 20 | cosTbl[i] = genCos(i) 21 | if n >= 1<= max { 20 | max = v 21 | } 22 | } 23 | return 24 | } 25 | 26 | func Plot(b image.Rectangle, d []float64) *image.RGBA { 27 | mAcc := 0.0 28 | wAcc := 0.0 29 | rAcc := 0.0 30 | im := image.NewRGBA(b) 31 | x := 0 32 | black := color.RGBA{A: 255} 33 | for x := b.Min.X; x < b.Max.X; x++ { 34 | im.Set(x, b.Min.Y, black) 35 | im.Set(x, b.Max.Y-1, black) 36 | } 37 | for y := b.Min.Y; y < b.Max.Y; y++ { 38 | im.Set(b.Min.X, y, black) 39 | im.Set(b.Max.X-1, y, black) 40 | } 41 | bb := image.Rect(b.Min.X+1, b.Min.Y+1, b.Max.X-1, b.Max.Y-1) 42 | subIm := im.SubImage(bb).(*image.RGBA) 43 | r := float64(bb.Dx()) / float64(len(d)) 44 | 45 | color := color.RGBA{ 46 | A: 180, 47 | R: 0, 48 | G: 200, 49 | B: 255} 50 | 51 | min, max := minMax(d) 52 | if min == 0 { 53 | min = 1e-10 54 | } 55 | 56 | //minDb := sample.ToDb(min) 57 | //maxDb := sample.ToDb(max) 58 | minDb, maxDb := min, max 59 | for j := 0; j < len(d); j++ { 60 | //mdb := sample.ToDb(d[j]) 61 | mdb := d[j] 62 | rAcc += r 63 | if rAcc < 1.0 { 64 | mAcc += mdb * r 65 | wAcc += r 66 | continue 67 | } 68 | _, ar := math.Modf(rAcc) 69 | mAcc += mdb * (r - ar) 70 | wAcc += r - ar 71 | v := mAcc / wAcc 72 | yr := (v - minDb) / (maxDb - minDb) 73 | Y := int(math.Floor(yr*float64(bb.Dy()) + 0.5)) 74 | for rAcc >= 1.0 { 75 | for y := 1; y <= Y; y++ { 76 | subIm.Set(b.Min.X+x, b.Max.Y-y, color) 77 | } 78 | rAcc -= 1.0 79 | x++ 80 | } 81 | mAcc = mdb * ar 82 | rAcc = ar 83 | wAcc = ar 84 | } 85 | return im 86 | } 87 | -------------------------------------------------------------------------------- /dct/plot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package dct 5 | 6 | import ( 7 | "fmt" 8 | "image" 9 | "image/png" 10 | "os" 11 | "testing" 12 | "time" 13 | 14 | "github.com/zikichombo/sound/freq" 15 | "github.com/zikichombo/sound/gen" 16 | "github.com/zikichombo/sound/ops" 17 | ) 18 | 19 | func TestPlot(t *testing.T) { 20 | n := ops.LimitDur(gen.Sin(820*freq.Hertz), time.Second) 21 | Ns := []int{128, 256, 512, 1024, 2048} 22 | b := image.Rect(0, 0, 768, 384) 23 | for _, N := range Ns { 24 | d := make([]float64, N) 25 | n.Receive(d) 26 | dct := New(N) 27 | dct.Do(d) 28 | p := 0.0 29 | for i, v := range d { 30 | d[i] = v //math.Abs(v) 31 | p += v * v 32 | } 33 | fmt.Printf("power %f\n", p) 34 | img := Plot(b, d) 35 | w, e := os.Create(fmt.Sprintf("dct-a2-%d.png", N)) 36 | if e != nil { 37 | t.Fatal(e) 38 | } 39 | if e := png.Encode(w, img); e != nil { 40 | t.Fatal(e) 41 | } 42 | w.Close() 43 | dct.Inv(d) 44 | img = Plot(b, d) 45 | w, e = os.Create(fmt.Sprintf("dctfx-a2-%d.png", N)) 46 | if e != nil { 47 | t.Fatal(e) 48 | } 49 | if e := png.Encode(w, img); e != nil { 50 | t.Fatal(e) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dct/t.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package dct 5 | 6 | import "math" 7 | 8 | // T encapsulates a DCT and its inverse. 9 | type T struct { 10 | p uint 11 | tmp []float64 12 | cosTbl [][]float64 13 | scf float64 14 | } 15 | 16 | // New creates a new T for transforming data of 17 | // length n. n must be a power of 2 or New panics. 18 | func New(n int) *T { 19 | p := uint(0) 20 | for 1<

= uint(len(cosTbl)) { 28 | ct = make([][]float64, p+1) 29 | for i := range cosTbl { 30 | ct[i] = cosTbl[i] 31 | } 32 | for i := uint(len(cosTbl)); i <= p; i++ { 33 | ct[i] = genCos(i) 34 | } 35 | } else { 36 | ct = cosTbl 37 | } 38 | res := &T{tmp: make([]float64, n), cosTbl: ct, p: p} 39 | res.scf = math.Sqrt(1.0 / float64(n/2)) 40 | return res 41 | } 42 | 43 | // Do performs "the" dct (type II dct) on d in place, with scaling. 44 | // 45 | // If |d| is not the size indicated in the call to New() which created t, 46 | // then Do panics. 47 | func (t *T) Do(d []float64) { 48 | if len(d) != len(t.tmp) { 49 | panic("wrong size input") 50 | } 51 | t.doRec(d, t.tmp, t.p) 52 | t.scale(d) 53 | } 54 | 55 | func (t *T) doRec(d, e []float64, p uint) { 56 | if p == 0 { 57 | return 58 | } 59 | n := len(d) 60 | h := n / 2 61 | top := n - 1 62 | var x, y float64 63 | cs := t.cosTbl[p] 64 | for i := 0; i < h; i++ { 65 | x = d[i] 66 | y = d[top-i] 67 | e[i] = x + y 68 | e[h+i] = (x - y) / (2 * cs[i]) 69 | } 70 | t.doRec(e[:h], d[:h], p-1) 71 | t.doRec(e[h:], d[:h], p-1) 72 | var i2, j int 73 | for i := 0; i < h-1; i++ { 74 | i2 = 2 * i 75 | d[i2] = e[i] 76 | j = h + i 77 | d[i2+1] = e[j] + e[j+1] 78 | } 79 | d[top-1] = e[h-1] 80 | d[top] = e[top] 81 | } 82 | 83 | // Inv inverts a transformed slice using the dct type III transform. 84 | // 85 | // If |d| is not the size indicated in the call to New() which created t, 86 | // then Inv panics. 87 | func (t *T) Inv(d []float64) { 88 | if len(d) != len(t.tmp) { 89 | panic("wrong input size") 90 | } 91 | d[0] /= 2 92 | t.invRec(d, t.tmp, t.p) 93 | t.scale(d) 94 | } 95 | 96 | func (t *T) scale(d []float64) { 97 | for i := range d { 98 | d[i] *= t.scf 99 | } 100 | } 101 | 102 | func (t *T) invRec(d, e []float64, p uint) { 103 | if p == 0 { 104 | return 105 | } 106 | n := len(d) 107 | h := n / 2 108 | top := n - 1 109 | e[0] = d[0] 110 | e[h] = d[1] 111 | var i2 int 112 | for i := 1; i < h; i++ { 113 | i2 = 2 * i 114 | e[i] = d[i2] 115 | e[h+i] = d[i2-1] + d[i2+1] 116 | } 117 | t.invRec(e[:h], d[:h], p-1) 118 | t.invRec(e[h:], d[:h], p-1) 119 | var x, y float64 120 | cs := t.cosTbl[p] 121 | for i := 0; i < h; i++ { 122 | x = e[i] 123 | y = e[h+i] / (2 * cs[i]) 124 | d[i] = x + y 125 | d[top-i] = x - y 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /dct/t_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package dct 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "math/rand" 10 | "testing" 11 | ) 12 | 13 | func TestTNaive(t *testing.T) { 14 | N := 1024 15 | Iters := 8 16 | d := make([]float64, N) 17 | tmp := make([]float64, N) 18 | ct := New(N) 19 | for i := 0; i < Iters; i++ { 20 | for i := 0; i < N; i++ { 21 | d[i] = rand.Float64() 22 | } 23 | copy(tmp, d) 24 | ct.Do(d) 25 | Naive(tmp) 26 | for i, v := range d { 27 | if math.Abs(v-tmp[i]) > 1e-12 { 28 | t.Errorf("%d: Lee %f Naive %f\n", i, v, tmp[i]) 29 | } 30 | } 31 | } 32 | } 33 | 34 | func TestCmp(t *testing.T) { 35 | d := []float64{-0.999984, -0.736924, 0.511211, -0.082700} 36 | dct := New(len(d)) 37 | dct.Do(d) 38 | } 39 | 40 | func TestTCos(t *testing.T) { 41 | N := 16 42 | d := make([]float64, N) 43 | rps := math.Pi / 16 44 | for i := 0; i < N; i++ { 45 | d[i] = math.Cos(float64(i) * rps) 46 | } 47 | fmt.Printf("in: %v\n", d) 48 | dct := New(N) 49 | dct.Do(d) 50 | fmt.Printf("dct: %v\n", d) 51 | dct.Inv(d) 52 | fmt.Printf("inv: %v\n", d) 53 | } 54 | -------------------------------------------------------------------------------- /dct/z.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package dct 5 | 6 | import ( 7 | "container/heap" 8 | "math" 9 | ) 10 | 11 | // Z provides an interface for compression via 12 | // coeficient selection 13 | type Z struct { 14 | nz []int 15 | pi []int 16 | vs []float64 17 | M float64 18 | op2, p2, lp2 float64 19 | } 20 | 21 | // NewZ create a new Z for cosine transforms 22 | // of size N. 23 | func NewZ(N int) *Z { 24 | res := &Z{} 25 | res.pi = make([]int, N) 26 | res.nz = make([]int, 0, N) 27 | res.M = float64(N) 28 | return res 29 | } 30 | 31 | // Init initializes z for the transform data d 32 | // and sets the current transform data of z to d. 33 | // Init then returns the power of d. 34 | // 35 | func (z *Z) Init(d []float64) float64 { 36 | pwr := 0.0 37 | z.pi = z.pi[:len(d)] 38 | for i, v := range d { 39 | pwr += v * v 40 | z.pi[i] = i 41 | } 42 | z.op2 = pwr 43 | z.p2 = pwr 44 | z.lp2 = 2 * pwr 45 | pwr /= z.M 46 | pwr = math.Sqrt(pwr) 47 | 48 | z.vs = d 49 | zh := (*zhp)(z) 50 | heap.Init(zh) 51 | 52 | z.nz = z.nz[:0] 53 | return pwr 54 | } 55 | 56 | // Top returns 57 | // 58 | // - the index of the most significant remaining coeficient, ci. 59 | // 60 | // - the ratio of the popped power to the original, pRatio. 61 | // 62 | // - the rate at which the ratio changed w.r.t. to the last Pop(), rate. 63 | func (z *Z) Top() (ci int, pRatio, rate float64) { 64 | ci = z.pi[0] 65 | v := z.vs[ci] 66 | org := math.Sqrt(z.op2 / z.M) 67 | d := z.p2 - v*v 68 | pRatio = math.Sqrt((z.op2-d)/z.M) / org 69 | d = z.lp2 - z.p2 70 | rate = math.Sqrt(d/z.M) / math.Sqrt(z.op2/z.M) 71 | return 72 | } 73 | 74 | // Pop pops a most significant coeficient from 75 | // the current transform data. 76 | func (z *Z) Pop() { 77 | zh := (*zhp)(z) 78 | ci := heap.Pop(zh).(int) 79 | z.nz = append(z.nz, ci) 80 | v := z.vs[ci] 81 | z.lp2 = z.p2 82 | z.p2 -= v * v 83 | return 84 | } 85 | 86 | // Zero zeros all un-popped coefficients 87 | func (z *Z) Zero() { 88 | for _, vi := range z.pi { 89 | z.vs[vi] = 0.0 90 | } 91 | } 92 | 93 | // Coefs returns the indices of the popped coeficients 94 | // storing the results in dst if there is space. 95 | func (z *Z) Coefs(dst []int) []int { 96 | dst = dst[:0] 97 | dst = append(dst, z.nz...) 98 | return dst 99 | } 100 | 101 | type zhp Z 102 | 103 | func (z *zhp) Less(i, j int) bool { 104 | return math.Abs(z.vs[z.pi[i]]) > math.Abs(z.vs[z.pi[j]]) 105 | } 106 | 107 | func (z *zhp) Swap(i, j int) { 108 | z.pi[i], z.pi[j] = z.pi[j], z.pi[i] 109 | } 110 | 111 | func (z *zhp) Len() int { 112 | return len(z.pi) 113 | } 114 | 115 | func (z *zhp) Push(v interface{}) { 116 | z.pi = append(z.pi, v.(int)) 117 | } 118 | 119 | func (z *zhp) Pop() interface{} { 120 | n := len(z.pi) - 1 121 | r := z.pi[n] 122 | z.pi = z.pi[:n] 123 | return r 124 | } 125 | -------------------------------------------------------------------------------- /dct/z_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package dct 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "math/rand" 10 | "testing" 11 | ) 12 | 13 | func TestZ(t *testing.T) { 14 | N := 1024 15 | d := make([]float64, N) 16 | z := NewZ(N) 17 | for i := range d { 18 | d[i] = 0.99*math.Cos(float64(i)*0.0234) + 0.01*(rand.Float64()-0.5) 19 | } 20 | ct := New(N) 21 | ct.Do(d) 22 | orgPwr := z.Init(d) 23 | t.Logf("initial power %f\n", orgPwr) 24 | i, ci := 0, 0 25 | var lpr, pr, r float64 26 | for i < N { 27 | ci, pr, r = z.Top() 28 | if r < 0.2 { 29 | break 30 | } 31 | t.Logf("%d: %d %f r %f rate %f\n", i, ci, d[ci], pr, r) 32 | z.Pop() 33 | i++ 34 | lpr = pr 35 | } 36 | z.Zero() 37 | pwr := 0.0 38 | for _, v := range d { 39 | pwr += v * v 40 | } 41 | pwr /= float64(len(d)) 42 | pwr = math.Sqrt(pwr) 43 | if math.Abs(pwr/orgPwr-lpr) > 1e-10 { 44 | t.Errorf("final power %f ratio %f expected %f\n", pwr, pwr/orgPwr, lpr) 45 | } 46 | } 47 | 48 | func TestProblem(t *testing.T) { 49 | var d = []float64{2.126846609940003e-08, 50 | -1.9429238307111518e-07, 4.0083187968775746e-07, -3.18344945071658e-07, 51 | -4.189423066236486e-07, 1.9653280105558224e-06, -3.877667495544301e-06, 52 | 5.364339813240804e-06, -5.428420081443619e-06, 2.944657808257034e-06, 53 | 2.1607013422908494e-06, -8.945888112066314e-06, 1.6051806596806273e-05, 54 | -2.084455445583444e-05, 2.093829789373558e-05, -1.5789008102728985e-05, 55 | 5.26436133441166e-06, 8.81815685715992e-06, -2.2116873878985643e-05, 56 | 3.1580013455823064e-05, -3.43189385603182e-05, 2.7483094527269714e-05, 57 | -1.2780014913005289e-05, -6.7707242124015465e-06, 2.8567645131261088e-05, 58 | -4.6346620365511626e-05, 5.5720924137858674e-05, -5.617501665255986e-05, 59 | 4.532853563432582e-05, -2.4820908947731368e-05, 7.29386329112458e-07, 60 | 2.471469997544773e-05, -4.663503204938024e-05, 5.795817924081348e-05, 61 | -5.905329089728184e-05, 4.895852543995716e-05, -2.568614399933722e-05, 62 | -3.848707819997799e-06, 3.5588047467172146e-05, -6.814824155298993e-05, 63 | 9.227096597896889e-05, -0.00010481775098014623, 0.00010734285751823336, 64 | -9.362259152112529e-05, 6.522569310618564e-05, -2.8237169317435473e-05, 65 | -2.0315472283982672e-05, 7.428289973177016e-05, -0.00012250729196239263, 66 | 0.0001633717183722183, -0.00018472722149454057, 0.00017164868768304586, 67 | -0.0001244281738763675, 3.862930680043064e-05, 8.390571747440845e-05, 68 | -0.00021764192206319422, 0.00034023079206235707, -0.0004265505413059145, 69 | 0.00043920031748712063, -0.00036495301173999906, 0.0002092648355755955, 70 | 1.6686981325619854e-05, -0.0002667483640834689, 0.0004822083574254066} 71 | for i := range d { 72 | d[i] *= 1e6 73 | } 74 | ct := New(len(d)) 75 | e := make([]float64, len(d)) 76 | copy(e, d) 77 | ct.Do(d) 78 | z := NewZ(len(d)) 79 | z.Init(d) 80 | last := math.Inf(1) 81 | for i := range d { 82 | ci, r, s := z.Top() 83 | fmt.Printf("%d ratio %f speed %f\n", i, r, s) 84 | if r >= 0.95 { 85 | break 86 | } 87 | if math.Abs(d[ci]) > last { 88 | t.Errorf("%d: got %f > %f", i, d[ci], last) 89 | } 90 | last = math.Abs(d[ci]) 91 | z.Pop() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Package dsp is an umbrella for audio dsp. 5 | // 6 | package dsp 7 | -------------------------------------------------------------------------------- /fft/bin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math" 8 | 9 | "github.com/zikichombo/sound/freq" 10 | ) 11 | 12 | // FreqBin gives the DFT frequency bin of frequency a in 13 | // a window of n-sample at rate sFreq 14 | func FreqBin(sFreq, aFreq freq.T, n int) int { 15 | fn := freq.T(n) 16 | df := sFreq / fn 17 | r, mr := aFreq/df, aFreq%df 18 | //fmt.Printf("FreqBin(s=%s f=%s, n=%d, b*=%d, mb=%d, d=%d mdf=%d)\n", sFreq, aFreq, n, r, mr, df, r*mdf) 19 | if mr >= df/2 { 20 | return int(r) + 1 21 | } 22 | return int(r) 23 | } 24 | 25 | // BinRange gives the range of frequencies in a DFT 26 | // frequency bin b where the DFT is based on an n-sample 27 | // window at rate sFreq. 28 | func BinRange(sFreq freq.T, n, b int) (l, u freq.T) { 29 | df := sFreq / freq.T(n) 30 | l = df * freq.T(b) 31 | u = l + df 32 | return l, u 33 | } 34 | 35 | // FreqAt gives the frequency at a floating point index i for 36 | // A FT for data of length n with sample frequency sFreq. 37 | func FreqAt(sFreq freq.T, n int, i float64) freq.T { 38 | ii, r := math.Modf(i) 39 | l, u := BinRange(sFreq, n, int(ii)) 40 | off := float64(u-l) * r 41 | return l + freq.T(int64(off)) 42 | } 43 | 44 | // WinSize gives the smallest positive window size (n, c) (n samples covering c 45 | // cycles of a) of a DFT applied to input signal with sample rate fS for frequency fA such 46 | // that a cycle appears after n samples. The cycle is approximate for period functions of frequency 47 | // fA but exact for some frequency within an error range around that (at worst within [fA .. fA + fE]). 48 | // 49 | // Special case: 50 | // if it overflows, it returns -1, -1 51 | // 52 | // For example, to find a good window size for detecting a=440 Hz in an s=17.6Khz sample, 53 | // we'd call WinSize(440Hz, 17.6KHz, 0.001Hz), giving n, and c. The window size n 54 | // in any signal of sampling rate s has c full cycles of any signal at frequency a, with 55 | // very little error because (sc % a) is small. As a result, using a DFT over 56 | // n samples will often have less error and noise in it, since the DFT assumes a cyclic 57 | // signal (actually stationary, cyclic applied infinitely satisfies). 58 | func WinSize(fS, fA, fE freq.T) (n, c int) { 59 | fc := freq.T(1) 60 | for { 61 | sc := fS * fc 62 | if sc < fS { 63 | return -1, -1 64 | } 65 | if sc%fA <= fE { 66 | return int(sc / fA), int(fc) 67 | } 68 | fc++ 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /fft/bin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/zikichombo/sound/freq" 11 | ) 12 | 13 | type tstCfg struct { 14 | n int 15 | s, f freq.T 16 | } 17 | 18 | var inDat = []tstCfg{ 19 | {100, 100 * freq.Hertz, freq.Hertz}, 20 | {100, 100 * freq.Hertz, freq.Hertz}, 21 | {100, 100 * freq.Hertz, 2 * freq.Hertz}, 22 | {100, 100 * freq.Hertz, 10 * freq.Hertz}, 23 | {100, 100 * freq.Hertz, 25 * freq.Hertz}} 24 | 25 | var alignDat = []tstCfg{ 26 | {1000, freq.Hertz, 2 * freq.Hertz / 3}, 27 | {100, 100 * freq.Hertz, 26*freq.Hertz - freq.MilliHertz}, 28 | {1000, 44100 * freq.Hertz, 440 * freq.Hertz}} 29 | 30 | func TestFreqBin(t *testing.T) { 31 | for i := 0; i < len(inDat); i += 2 { 32 | cfg := &inDat[i] 33 | b := FreqBin(cfg.s, cfg.f, cfg.n) 34 | l, u := BinRange(cfg.s, cfg.n, b) 35 | fmt.Printf("s=%s n=%d f=%s b=%d [%s, %s)\n", cfg.s, cfg.n, cfg.f, b, l, u) 36 | } 37 | for i := 0; i < len(alignDat); i++ { 38 | cfg := &alignDat[i] 39 | b := FreqBin(cfg.s, cfg.f, cfg.n) 40 | l, u := BinRange(cfg.s, cfg.n, b) 41 | fmt.Printf("s=%s n=%d f=%s b=%d [%s, %s)\n", cfg.s, cfg.n, cfg.f, b, l, u) 42 | } 43 | } 44 | 45 | func TestWinSize(t *testing.T) { 46 | for _, err := range []freq.T{1 * freq.MicroHertz, 10 * freq.MicroHertz, 100 * freq.MicroHertz, freq.MilliHertz, 10 * freq.MilliHertz} { 47 | for i := 0; i < len(inDat); i += 2 { 48 | cfg := &inDat[i] 49 | _, c := WinSize(cfg.s, cfg.f, err) 50 | if c != 1 { 51 | t.Errorf("wrong number of cyles: %d\n", c) 52 | } 53 | //fmt.Printf("%s: find signal of %s in %d samples (%d cylcles) at %s\n", err, cfg.f, sz, c, cfg.s) 54 | } 55 | for i := 0; i < len(alignDat); i++ { 56 | cfg := &alignDat[i] 57 | _, c := WinSize(cfg.s, cfg.f, err) 58 | if (freq.T(c)*cfg.s)%cfg.f > err { 59 | t.Errorf("bad error") 60 | } 61 | //fmt.Printf("%s: find signal of %s in %d samples (%d cycles) at %s\n", err, cfg.f, sz, c, cfg.s) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /fft/chirpz.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math" 8 | "math/cmplx" 9 | ) 10 | 11 | // chirps are extra factors used in bluesteins algorithm 12 | // of the form e^{i*pi*k*k/N} with k in range 0...padN 13 | // i sqrt(-1). 14 | type chirpz struct { 15 | D []complex128 16 | tD []complex128 17 | } 18 | 19 | func newChirpz(n, padN int, twids *twiddles) *chirpz { 20 | res := &chirpz{ 21 | D: make([]complex128, padN), 22 | tD: make([]complex128, padN)} 23 | N := float64(n) 24 | M := padN 25 | res.D[0] = complex(1, 0) // = cos(0) i*sin(0) 26 | for i := 1; i < n; i++ { 27 | r := complex(0, -math.Pi*float64(i*i)/N) 28 | c := cmplx.Exp(r) 29 | if !twids.inv { 30 | c = cmplx.Conj(c) 31 | } 32 | res.D[i] = c 33 | res.D[M-i] = c 34 | } 35 | for i := n; i < M-n; i++ { 36 | res.D[i] = 0.0 37 | } 38 | copy(res.tD, res.D) 39 | r2(res.tD, twids, true) 40 | return res 41 | } 42 | 43 | func (c *chirpz) inv(i int) complex128 { 44 | return cmplx.Conj(c.D[i]) 45 | } 46 | -------------------------------------------------------------------------------- /fft/chirpz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import "testing" 7 | 8 | func TestChirpz(t *testing.T) { 9 | c := newChirpz(10, 32, newTwiddles(32, false)) 10 | _ = c 11 | } 12 | -------------------------------------------------------------------------------- /fft/convolve_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | type convolveCfg struct { 12 | a, b []complex128 13 | m int 14 | } 15 | 16 | func (c *convolveCfg) test(t *testing.T) { 17 | d := c.dumb() 18 | dd := make([]complex128, len(d)) 19 | //Convolve(c.a, c.b, dd) 20 | for i := range d { 21 | if d[i] != dd[i] { 22 | t.Errorf("convolve result mismatch at %d: %f != %f", i, dd[i], d[i]) 23 | } 24 | } 25 | } 26 | 27 | func mod(i, j int) int { 28 | if i >= 0 { 29 | return i % j 30 | } 31 | return (j + (i % j)) % j 32 | } 33 | 34 | func rev(x []complex128) { 35 | n := len(x) 36 | h := n / 2 37 | n-- 38 | 39 | for i := 0; i < h; i++ { 40 | x[i], x[n-i] = x[n-i], x[i] 41 | } 42 | } 43 | 44 | func (c *convolveCfg) dumb() []complex128 { 45 | res := make([]complex128, c.m) 46 | a, b := c.a, c.b 47 | if len(a) < len(b) { 48 | a, b = b, a 49 | } 50 | rev(b) 51 | n := len(a) 52 | m := len(b) 53 | 54 | fmt.Printf("indices:\n") 55 | for i := range res { 56 | v := 0i 57 | for j := range a { 58 | k := (j + n) % n 59 | k -= n - m 60 | if k < 0 { 61 | continue 62 | } 63 | fmt.Printf("\t*(%d) a[%d] * b[%d] aL%d bL%d\n", i, j, k, len(a), len(b)) 64 | v += a[j] * b[k] 65 | } 66 | res[i] = v 67 | fmt.Printf("result: %f\n", v) 68 | } 69 | return res 70 | } 71 | 72 | var convolveCfgs = [...]convolveCfg{ 73 | {[]complex128{0i}, []complex128{0i}, 1}, 74 | {[]complex128{0i, 1i, 0i}, []complex128{0i, 1i, 0i}, 3}, 75 | {[]complex128{1i, 11i, 0 + 2i}, []complex128{2i, 3i, 5i}, 3}, 76 | {[]complex128{2i, 3i, 5i}, []complex128{0i, 0i, 1i}, 3}, 77 | {[]complex128{2i, 3i, 5i}, []complex128{2i, 0i, 0i}, 3}} 78 | 79 | func testConvolve(t *testing.T) { 80 | for i := range convolveCfgs { 81 | c := &convolveCfgs[i] 82 | c.test(t) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /fft/dilate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math" 8 | "math/cmplx" 9 | "testing" 10 | 11 | "github.com/zikichombo/sound/freq" 12 | "github.com/zikichombo/sound/sndbuf" 13 | ) 14 | 15 | func TestDilate(t *testing.T) { 16 | L := 4096 17 | F := 1024 * freq.Hertz * 16 18 | fa := 24 * freq.Hertz 19 | fb := fa * 3 20 | fc := fa * 5 21 | waves := sndbuf.New(44100*freq.Hertz, 1) 22 | for i := 0; i < L; i++ { 23 | v := 0.0 24 | for _, f := range []freq.T{fa, fb, fc} { 25 | r := f.RadsPerAt(F) 26 | v += math.Sin(float64(i) * r) 27 | } 28 | waves.Send([]float64{v}) 29 | } 30 | waves.Seek(0) 31 | d := make([]float64, L) 32 | waves.Receive(d) 33 | dc := make([]complex128, L) 34 | for i := range d { 35 | dc[i] = complex(d[i], 0) 36 | } 37 | 38 | ft := New(L) 39 | ft.Do(dc) 40 | Dilate(dc, 5, 2) 41 | 42 | waves.Seek(0) 43 | fa = (fa * 5) / 2 44 | fb = (fb * 5) / 2 45 | fc = (fc * 5) / 2 46 | for i := 0; i < L; i++ { 47 | v := 0.0 48 | for _, f := range []freq.T{fa, fb, fc} { 49 | r := f.RadsPerAt(F) 50 | v += math.Sin(float64(i) * r) 51 | } 52 | waves.Send([]float64{v}) 53 | } 54 | waves.Seek(0) 55 | waves.Receive(d) 56 | ddc := make([]complex128, len(d)) 57 | for i := range d { 58 | ddc[i] = complex(d[i], 0) 59 | } 60 | 61 | ft.Do(ddc) 62 | ttlErr := 0 63 | for i := 0; i < L/2; i++ { 64 | m1, _ := cmplx.Polar(dc[i]) 65 | m2, _ := cmplx.Polar(ddc[i]) 66 | if math.Abs(m2-m1) > 0.001 { 67 | ttlErr++ 68 | } 69 | } 70 | // Many places cite this dilate mechanism as a pitch shift. But it is not 71 | // purely a pitch shift, as 1) frequencies in the signal are mapped to sinc shaped 72 | // functions in the quantized fft frequencies, and 2) edge effects. For 73 | // 1), the sinc function determining the magnitude of a frequency bin for 74 | // single sinusoid is at a different distance from the center frequency 75 | // after a pitch shift. For 2), window functions or window size at LCM 76 | // of sinusoid wavelengths can help. 77 | // 78 | // The bound 0.05 was just found manually. 79 | if float64(ttlErr)/float64(L/2) > 0.05 { 80 | t.Errorf("too many errors") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /fft/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Package fft provides support for 1 dimensional fast fourier transform and 5 | // spectra. 6 | // 7 | // Package fft is part of http://zikichombo.org 8 | // 9 | // Fast fourier transforms provide frequency domain representation of time 10 | // domain signals. Package fft supports a fairly efficient fourier transform 11 | // implementation with a lot of simplicity and convenience. 12 | // 13 | // 14 | // Features 15 | // 16 | // Package fft is designed primarily for repeated usage on a data stream. As 17 | // such, the incremental interface supports the creation of buffers which 18 | // automatically have size and capacity which guarantee no copying or 19 | // allocations during executation without the user needing to worry about the 20 | // details of the sizes and capacities. 21 | // 22 | // Package fft provides an efficient real-only interface for even transform 23 | // sizes with spectra represented in a half complex format like fftw. An odd 24 | // transform size real-only interface is also supported, but is not as efficient 25 | // as the even transform size real-only interface. 26 | // 27 | // The interface guarantees the Parseval equation, which states the sum of 28 | // squares of the amplitudes in the time domain equals the sum of squares of 29 | // the frequency coeficients in the frequency domain. It also guarantees that 30 | // the inverse transform is an inverse without the user needing to worry about 31 | // scaling. 32 | // 33 | // Non Features 34 | // 35 | // Package fft is designed exclusively for 1d data. Support for matrices is a 36 | // non-goal of this package. 37 | // 38 | // Algorithms 39 | // 40 | // The implementation uses in place decimation in time binary radix 2 Cooley 41 | // Tuckey for sizes of powers of 2, and Bluestein algorithm otherwise. Twiddle 42 | // factors are pre-computed, and attention is paid to minimize allocations and 43 | // copying, both internally and in the interface. Package fft provides O(N Log 44 | // N) transforms for all inputs and allows for in place transforms. The Real 45 | // only interface uses the complex interface for half-sized inputs together 46 | // with some O(N) pre/post processing. 47 | // 48 | package fft 49 | -------------------------------------------------------------------------------- /fft/factor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math" 8 | "math/big" 9 | ) 10 | 11 | // nb this is unused, but can be useful if/when we add factor based 12 | // general radix cooley tuckey implementations 13 | var primes = []int{ 14 | 2, 3, 5, 7, 11, 13, 17, 23, 29, 31, 37, 41, 43, 47, 51, 53, 57, 59, 15 | 61, 67, 71, 73, 79} 16 | 17 | func init() { 18 | factor(101483) 19 | } 20 | 21 | func factor(n int) (int, int, int) { 22 | t := 1 23 | for n%2 == 0 { 24 | n /= 2 25 | t *= 2 26 | } 27 | if n == 1 || n == 3 || n == 5 || n == 7 { 28 | return 1, n, t 29 | } 30 | var p int 31 | L := int(math.Sqrt(float64(n))) + 1 32 | for _, p = range primes { 33 | if p > L { 34 | return 1, n, t 35 | } 36 | if n%p == 0 { 37 | return p, n / p, t 38 | } 39 | } 40 | p += 2 41 | x := new(big.Int) 42 | x.SetInt64(int64(p)) 43 | const nb = 2 44 | for x.Int64() < int64(L) { 45 | p = int(x.Int64()) 46 | // nb according to docs I think this should be 100% accurate in our range. 47 | if x.ProbablyPrime(nb) { 48 | //fmt.Printf("new prime %d\n", p) 49 | primes = append(primes, p) 50 | if n%p == 0 { 51 | return p, n / p, t 52 | } 53 | } 54 | if p > L { 55 | return 1, n, t 56 | } 57 | x.SetInt64(int64(p) + 2) 58 | } 59 | return 1, n, t 60 | } 61 | 62 | func log2(i int) uint { 63 | r := uint(0) 64 | for i > 1< q { 85 | for i := h; i >= 1; i-- { 86 | dst := (i * p) / q 87 | if dst > h { 88 | d[i] = 0i 89 | continue 90 | } 91 | v := d[i] 92 | d[i] = 0i 93 | d[dst] += v 94 | } 95 | for i := n - 1; i > h; i-- { 96 | dst := (i * p) / q 97 | if dst >= n { 98 | d[i] = 0i 99 | continue 100 | } 101 | v := d[i] 102 | d[i] = 0i 103 | d[dst] += v 104 | } 105 | return 106 | } 107 | for i := 1; i <= h; i++ { 108 | dst := (i * p) / q 109 | if dst > h { 110 | d[i] = 0i 111 | continue 112 | } 113 | v := d[i] 114 | d[i] = 0i 115 | d[dst] += v 116 | } 117 | for i := h + 1; i < n; i++ { 118 | dst := (i * p) / q 119 | if dst > n { 120 | d[i] = 0i 121 | continue 122 | } 123 | v := d[i] 124 | d[i] = 0i 125 | d[dst] += v 126 | } 127 | } 128 | 129 | // older stuff 130 | 131 | // radix 2, in place recursive 132 | func radix2(src []float64, dst []complex128, N, stride int, inv float64) { 133 | if N == 1 { 134 | dst[0] = complex(src[0], 0.0) 135 | return 136 | } 137 | h := N / 2 138 | radix2(src, dst[:h], h, stride*2, inv) 139 | radix2(src[stride:], dst[h:], h, stride*2, inv) 140 | 141 | // recombine 142 | for i := 0; i < h; i++ { 143 | ed := dst[i] 144 | od := dst[i+h] 145 | exp := complex(0, inv*math.Pi*2.0*float64(i)/float64(N)) 146 | c := cmplx.Exp(exp) 147 | dst[i] = ed + c*od 148 | dst[i+h] = ed - c*od 149 | } 150 | } 151 | 152 | // P is prime. 153 | 154 | // TBD(wsc) Rader? 155 | func slowft(src []float64, dst []complex128, P, stride int, inv float64) { 156 | if P == 1 { 157 | dst[0] = complex(src[0], 0) 158 | return 159 | } 160 | var a, s, fi float64 161 | var c complex128 162 | 163 | lim := P * stride 164 | n := float64(len(dst)) 165 | A := inv * 2.0 * math.Pi / n 166 | for i := 0; i < len(dst); i++ { 167 | fi = float64(i) 168 | c = 0i 169 | for j := 0; j < lim; j += stride { 170 | a = A * float64(j/stride) * fi 171 | s = src[j] 172 | c += complex(s*math.Cos(a), s*math.Sin(a)) 173 | } 174 | dst[i] = c 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /fft/hc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math/cmplx" 8 | ) 9 | 10 | // HalfComplex is a format for storing complex spectra of real data of length N 11 | // in a []float64 of length N. Such spectra have Hermitian symmetry. 12 | // 13 | // Since the spectrum is so symmetric, all the information will fit, provided 14 | // we reflect around the points correctly. 15 | // 16 | // The format is (like in fftw): 17 | // 18 | // r0, r1, ..., r_{n/2}, i_{(n+1)/2 - 1}, ..., i2, i1 19 | // 20 | // for complex values rn + in, n an integer in [0...N/2] 21 | // 22 | // Some things to note 23 | // 24 | // - Due to the symmetry, i0 is always 0 and i_{n/2} is always 0 if N is even. 25 | // 26 | // - The number of reals is 2 greater than the number of imaginaries when N is 27 | // even 28 | // 29 | // - The number of reals is 1 greater than the number of imaginaries when N is 30 | // odd because i_{n/2} doesn't exist. 31 | // 32 | type HalfComplex []float64 33 | 34 | // Cmplx returns the complex128 representation of element i. 35 | func (h HalfComplex) Cmplx(i int) complex128 { 36 | N := len(h) 37 | re := h[i] 38 | im := 0.0 39 | if i == 0 || 2*i == N { 40 | return complex(re, im) 41 | } 42 | im = h[N-i] 43 | return complex(re, im) 44 | } 45 | 46 | // SetCmplx sets the complex number i to c in h. 47 | func (h HalfComplex) SetCmplx(i int, c complex128) { 48 | N := len(h) 49 | h[i] = real(c) 50 | if i == 0 || 2*i == N { 51 | return 52 | } 53 | h[N-i] = imag(c) 54 | } 55 | 56 | // Real returns the real part of the complex number at i. 57 | func (h HalfComplex) Real(i int) float64 { 58 | return h[i] 59 | } 60 | 61 | // SetReal sets the real part of the complex number at i. 62 | func (h HalfComplex) SetReal(i int, v float64) { 63 | h[i] = v 64 | } 65 | 66 | // Imag returns the imaginary part of the complex number at i. 67 | func (h HalfComplex) Imag(i int) float64 { 68 | N := len(h) 69 | if i == 0 || 2*i == N { 70 | return 0 71 | } 72 | return h[N-i] 73 | } 74 | 75 | // SetImag sets the imaginary part of the complex number at i to v. 76 | // Since all imaginary parts at complex number 0 and h.Len()/2 are 77 | // 0, if i == 0 or h.Len()/2, then SetImag is a no-op. 78 | func (h HalfComplex) SetImag(i int, v float64) { 79 | N := len(h) 80 | if i == 0 || 2*i == N { 81 | return 82 | } 83 | h[N-i] = v 84 | } 85 | 86 | // Len returns the number of complex numbers in h, which does not 87 | // include elements with symmetric pairs. 88 | func (h HalfComplex) Len() int { 89 | n := len(h) 90 | return n/2 + 1 91 | } 92 | 93 | // ToCmplx fills d with complex numbers stored in h. 94 | // 95 | // if len(h) != len(d), then ToCmplx panics. 96 | func (h HalfComplex) ToCmplx(d []complex128) { 97 | if len(h) != len(d) { 98 | panic("size mismatch") 99 | } 100 | if len(h) == 0 { 101 | return 102 | } 103 | d[0] = complex(h[0], 0.0) 104 | N := len(d) 105 | M := N / 2 106 | if M+M != N { 107 | M++ 108 | } 109 | for i := 1; i < M; i++ { 110 | d[i] = complex(h[i], h[N-i]) 111 | d[N-i] = cmplx.Conj(d[i]) 112 | } 113 | if M+M == N { 114 | d[M] = complex(h[M], 0.0) 115 | } 116 | } 117 | 118 | // ToPolar fills mag, phase with the magnitude and phase 119 | // of complex numbers stored in h 120 | func (h HalfComplex) ToPolar(mag, ph []float64) { 121 | if len(h) != len(mag) || len(mag) != len(ph) { 122 | panic("size mismatch") 123 | } 124 | if len(h) == 0 { 125 | return 126 | } 127 | mag[0], ph[0] = cmplx.Polar(complex(h[0], 0.0)) 128 | N := len(h) 129 | M := N / 2 130 | if M+M != N { 131 | M++ 132 | } 133 | var c complex128 134 | for i := 1; i < M; i++ { 135 | c = complex(h[i], h[N-i]) 136 | mag[i], ph[i] = cmplx.Polar(c) 137 | mag[N-i], ph[N-i] = cmplx.Polar(cmplx.Conj(c)) 138 | } 139 | if M+M == N { 140 | mag[M], ph[M] = cmplx.Polar(complex(h[M], 0.0)) 141 | } 142 | } 143 | 144 | // FromCmplx places a complex spectrum of a real sequence in d. 145 | // 146 | // FromCmplx panics if len(h) != len(d). 147 | // 148 | // FromCmplx does not check that d is in the symmetric form 149 | // of a DFT of real data. 150 | func (h HalfComplex) FromCmplx(d []complex128) { 151 | if len(h) != len(d) { 152 | panic("size mismatch") 153 | } 154 | if len(h) == 0 { 155 | return 156 | } 157 | h[0] = real(d[0]) 158 | N := len(d) 159 | M := N / 2 160 | var c complex128 161 | if M+M != N { 162 | M++ 163 | } 164 | for i := 1; i < M; i++ { 165 | c = d[i] 166 | h[i] = real(c) 167 | h[N-i] = imag(c) 168 | } 169 | if M+M == N { 170 | h[M] = real(d[M]) 171 | } 172 | } 173 | 174 | // FromPolar places a complex spectrum of a real sequence in h. 175 | // 176 | // FromCmplx panics if len(h) != len(d). 177 | // 178 | // FromCmplx does not check that d is in the symmetric form 179 | // of a DFT of real data. 180 | func (h HalfComplex) FromPolar(mag, ph []float64) { 181 | if len(h) != len(mag) || len(mag) != len(ph) { 182 | panic("size mismatch") 183 | } 184 | if len(h) == 0 { 185 | return 186 | } 187 | h[0] = real(cmplx.Rect(mag[0], ph[0])) 188 | N := len(h) 189 | M := N / 2 190 | var c complex128 191 | if M+M != N { 192 | M++ 193 | } 194 | for i := 1; i < M; i++ { 195 | c = cmplx.Rect(mag[0], ph[0]) 196 | h[i] = real(c) 197 | h[N-i] = imag(c) 198 | } 199 | if M+M == N { 200 | h[M] = real(cmplx.Rect(mag[M], ph[M])) 201 | } 202 | } 203 | 204 | // MulElems computes the elementwise multiplication of a and b, placing 205 | // the result in a and returning it. MulElems panics if a.Len() != b.Len(). 206 | func (a HalfComplex) MulElems(b HalfComplex) HalfComplex { 207 | if len(a) != len(b) { 208 | panic("size mismatch") 209 | } 210 | N := a.Len() 211 | var ca, cb complex128 212 | for i := 0; i < N; i++ { 213 | ca = a.Cmplx(i) 214 | cb = b.Cmplx(i) 215 | a.SetCmplx(i, ca*cb) 216 | } 217 | return a 218 | } 219 | -------------------------------------------------------------------------------- /fft/hc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math/cmplx" 8 | "math/rand" 9 | "testing" 10 | ) 11 | 12 | func TestHalfComplex(t *testing.T) { 13 | d := make([]float64, 64) 14 | for i := range d { 15 | d[i] = rand.Float64() 16 | } 17 | hc := HalfComplex(d) 18 | for i := 0; i < hc.Len(); i++ { 19 | c := hc.Cmplx(i) 20 | hc.SetCmplx(i, cmplx.Conj(c)) 21 | hc.SetCmplx(i, cmplx.Conj(hc.Cmplx(i))) 22 | if hc.Cmplx(i) != c { 23 | t.Errorf("get/set") 24 | } 25 | } 26 | } 27 | 28 | func TestHCToFromCmplx(t *testing.T) { 29 | for _, N := range []int{64, 65} { 30 | d := make([]float64, N) 31 | e := make([]float64, N) 32 | c := make([]complex128, N) 33 | for i := range d { 34 | d[i] = rand.Float64() 35 | e[i] = d[i] 36 | } 37 | hc := HalfComplex(d) 38 | hc.ToCmplx(c) 39 | for i := range hc { 40 | hc[i] = 0.0 41 | } 42 | hc.FromCmplx(c) 43 | for i, v := range hc { 44 | if v != e[i] { 45 | t.Errorf("N=%d i=%d after to/from cmplx got %f not %f\n", N, i, v, e[i]) 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fft/pad.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | func pad(d []complex128, toLen int) []complex128 { 7 | n := len(d) 8 | c := cap(d) 9 | if n >= toLen { 10 | return d[:toLen] 11 | } 12 | if c < toLen { 13 | t := make([]complex128, toLen) 14 | copy(t, d) 15 | return t 16 | } 17 | d = d[:toLen] 18 | for i := n; i < toLen; i++ { 19 | d[i] = 0i 20 | } 21 | return d 22 | } 23 | -------------------------------------------------------------------------------- /fft/r2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | // in place, radix 2 decimation in time Cooley-Tuckey 7 | // 8 | // len(d) should be power of 2 9 | // 10 | func r2(d []complex128, tw *twiddles, sc bool) { 11 | N := len(d) 12 | M := 2 13 | if !is2pow(N) { 14 | panic("length not power of 2") 15 | } 16 | if N == 1 { 17 | return 18 | } 19 | if N == 2 { 20 | // problem with twiddles or something, treat explicitly. 21 | e := d[0] 22 | co := d[1] 23 | d[0] = e + co 24 | d[1] = e - co 25 | if sc { 26 | scale(d) 27 | } 28 | return 29 | } 30 | revBinPermute(d) 31 | var q, r, a, b, H int 32 | var c, e, co complex128 33 | for M <= N { 34 | H = M / 2 35 | for q = 0; q < N; q += M { 36 | for r = 0; r < H; r++ { 37 | a = q + r 38 | b = a + H 39 | c = tw.cmplxQ(r, M) 40 | e, co = d[a], d[b]*c 41 | d[a], d[b] = e+co, e-co 42 | } 43 | } 44 | M *= 2 45 | } 46 | if sc { 47 | scale(d) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fft/r2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "testing" 10 | 11 | "github.com/zikichombo/sound/freq" 12 | ) 13 | 14 | func gnr() []complex128 { 15 | N := 256 16 | res := make([]complex128, N) 17 | f := 1024 * freq.Hertz 18 | fa, fb, fc, fd := 128*freq.Hertz, 256*freq.Hertz, 64*freq.Hertz, 512*freq.Hertz 19 | for i := 0; i < N; i++ { 20 | fi := float64(i) 21 | va := math.Sin(fa.RadsPerAt(f) * fi) 22 | vb := 2 * math.Sin(fb.RadsPerAt(f)*fi) 23 | vc := 5 * math.Sin(fc.RadsPerAt(f)*fi) 24 | vd := 7 * math.Sin(fd.RadsPerAt(f)*fi) 25 | res[i] = complex(va+vb+vc+vd, 0) 26 | } 27 | return res 28 | } 29 | 30 | func TestRadix2(t *testing.T) { 31 | d := gnr() 32 | dc := make([]complex128, len(d)) 33 | copy(dc, d) 34 | 35 | r2(d, newTwiddles(len(d), false), true) 36 | r2(d, newTwiddles(len(d), true), true) 37 | eps := 0.001 38 | for i := range d { 39 | re := math.Abs(real(d[i]) - real(dc[i])) 40 | ie := math.Abs(imag(d[i]) - imag(dc[i])) 41 | if re+ie > eps { 42 | l, u := BinRange(1024*freq.Hertz, len(d), i) 43 | fmt.Printf("bin %d: [%s..%s) %f\n", i, l, u, d[i]) 44 | t.Errorf("transform and back wasn't identity: %f != %f\n", d[i], dc[i]) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /fft/real.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math" 8 | "math/cmplx" 9 | ) 10 | 11 | // Real computes an FFT for a Real only data. 12 | // 13 | // For even length transforms, the implementation 14 | // uses a complex FFT of size N/2 and some pre/post processing. 15 | // For odd length transforms, the implementtion uses a complex 16 | // FFT of size N. 17 | type Real struct { 18 | ft *T // half sized for even 19 | n int // 20 | cBuf []complex128 // 21 | twidz []complex128 // only for even 22 | scaler float64 // only for even 23 | } 24 | 25 | // NewReal creates a new FFT transformer for 26 | // float data of length n. 27 | func NewReal(n int) *Real { 28 | if n&1 == 0 { 29 | m := n / 2 30 | res := &Real{ft: New(m)} 31 | res.n = n 32 | res.cBuf = res.ft.Win(nil) 33 | res.ft.Scale(false) 34 | twidz := make([]complex128, m) 35 | N := float64(n) 36 | for i := range twidz { 37 | s, c := math.Sincos(float64(i) * 2.0 * math.Pi / N) 38 | twidz[i] = complex(c, -s) 39 | } 40 | res.twidz = twidz 41 | res.scaler = 1.0 / math.Sqrt(N) 42 | return res 43 | } 44 | res := &Real{ft: New(n)} 45 | res.n = n 46 | res.cBuf = res.ft.Win(nil) 47 | return res 48 | } 49 | 50 | // Do performs a DFT on real data in d. 51 | // 52 | // d must be the size specified in the call to NewReal() 53 | // which created r, or Do will panic. 54 | // 55 | // Do operates in place, overwriting d. Do returns 56 | // d overwritten as (i.e. cast to) a HalfComplex object. 57 | // 58 | func (r *Real) Do(d []float64) HalfComplex { 59 | if r.n&1 == 0 { 60 | return r.evenDo(d) 61 | } 62 | return r.oddDo(d) 63 | } 64 | 65 | func (r *Real) evenDo(d []float64) HalfComplex { 66 | r.pack(d) 67 | r.ft.Do(r.cBuf) 68 | hc := r.toHC(d) 69 | if r.scaler == 1.0 { 70 | return hc 71 | } 72 | for i := range hc { 73 | hc[i] *= r.scaler 74 | } 75 | return hc 76 | } 77 | 78 | func (r *Real) oddDo(d []float64) HalfComplex { 79 | for i, v := range d { 80 | r.cBuf[i] = complex(v, 0.0) 81 | } 82 | r.ft.Do(r.cBuf) 83 | res := HalfComplex(d) 84 | res.FromCmplx(r.cBuf) 85 | return res 86 | } 87 | 88 | // Inv computes the inverse transform of a real data 89 | // from a HalfComplex object. 90 | // 91 | // Inv operates in place but returns the same data as hc, cast to 92 | // a []float64. 93 | func (r *Real) Inv(hc HalfComplex) []float64 { 94 | if r.n&1 == 0 { 95 | return r.evenInv(hc) 96 | } 97 | return r.oddInv(hc) 98 | } 99 | 100 | func (r *Real) evenInv(hc HalfComplex) []float64 { 101 | if r.scaler != 1.0 { 102 | for i := range hc { 103 | hc[i] /= r.scaler 104 | } 105 | } 106 | r.fromHC(hc) 107 | r.ft.Inv(r.cBuf) 108 | res := []float64(hc) 109 | r.unpack(res) 110 | if r.scaler != 1.0 { 111 | sc := 1.0 / float64(len(r.cBuf)) 112 | for i := range res { 113 | res[i] *= sc 114 | } 115 | } 116 | return res 117 | } 118 | 119 | func (r *Real) oddInv(hc HalfComplex) []float64 { 120 | hc.ToCmplx(r.cBuf) 121 | r.ft.Inv(r.cBuf) 122 | for i, c := range r.cBuf { 123 | hc[i] = real(c) 124 | } 125 | return []float64(hc) 126 | } 127 | 128 | // Scale sets whether or not r scales the transform results. 129 | // Scale returns whether or not r was configured to scale 130 | // the transform results prior to calling Scale. 131 | func (r *Real) Scale(v bool) { 132 | if r.n&1 == 0 { 133 | if !v { 134 | r.scaler = 1.0 135 | } else { 136 | r.scaler = 1.0 / math.Sqrt(float64(r.n)) 137 | } 138 | return 139 | } 140 | r.ft.Scale(v) 141 | } 142 | 143 | // N returns the length of the arguments to the transform 144 | // implemented by r. 145 | func (r *Real) N() int { 146 | return r.n 147 | } 148 | 149 | // Translate r.cBuf into halfcomplex in d. 150 | // 151 | // Method from The DSP Book. 152 | // some bugs fixed (wrong sign of 153 | // twiddles, lack of appropriate DC scaling, index off by one error). 154 | // 155 | // http://dsp-book.narod.ru/FFTBB/0270_PDF_C14.pdf 156 | // 157 | func (r *Real) toHC(d []float64) HalfComplex { 158 | const ( 159 | halfR = complex(0.5, 0) 160 | halfI = complex(0, 0.5) 161 | ) 162 | N := len(d) 163 | if N != r.n { 164 | panic("invalid length") 165 | } 166 | res := HalfComplex(d) 167 | if N == 0 { 168 | return res 169 | } 170 | cb := r.cBuf 171 | h := len(cb) 172 | 173 | a := cb[0] 174 | f0 := halfR * a 175 | g0 := -halfI * a 176 | shift := r.twidz[0] 177 | res.SetCmplx(0, complex(2.0, 0.0)*(f0+shift*g0)) 178 | if h+h == N { 179 | res[h] = 2.0 * real(f0-g0) 180 | } 181 | 182 | for i := 1; i < h; i++ { 183 | a, b := cb[i], cmplx.Conj(cb[h-i]) 184 | fi := halfR * (a + b) 185 | gi := halfI * (b - a) 186 | shift := r.twidz[i] 187 | xi := fi + shift*gi 188 | res.SetCmplx(i, xi) 189 | } 190 | return res 191 | } 192 | 193 | func (r *Real) fromHC(hc HalfComplex) { 194 | // Method derived in part from literature and in part trial and error. 195 | // We derive backwards fi, gi from Do, and from that derive 196 | // the original complex value output from the forward fft. 197 | const ( 198 | halfR = complex(0.5, 0) 199 | halfI = complex(0, 0.5) 200 | ) 201 | N := len(hc) 202 | if N != r.n { 203 | panic("invalid HalfComplex length") 204 | } 205 | if N == 0 { 206 | return 207 | } 208 | h := len(r.cBuf) 209 | a, b := hc.Cmplx(0), 0i 210 | f := halfR * (a + b) 211 | g := cmplx.Conj(r.twidz[0]) * (a - f) 212 | r.cBuf[0] = halfR * (f/halfR - g/halfI) 213 | var ny float64 214 | if h+h == N { 215 | ny = 0.5 * hc[h] 216 | } else { 217 | ny = 0.5 * hc[h-1] 218 | } 219 | r.cBuf[0] = complex(real(r.cBuf[0])+ny, imag(r.cBuf[0])-ny) 220 | var j int 221 | for i := 1; i < h; i++ { 222 | j = h - i 223 | a, b := hc.Cmplx(i), cmplx.Conj(hc.Cmplx(j)) 224 | fi := halfR * (a + b) 225 | gi := cmplx.Conj(r.twidz[i]) * (a - fi) 226 | r.cBuf[i] = halfR * (fi/halfR - gi/halfI) 227 | } 228 | } 229 | 230 | func (r *Real) pack(d []float64) { 231 | cb := r.cBuf 232 | end := len(cb) 233 | for i := 0; i < end; i++ { 234 | cb[i] = complex(d[2*i], d[2*i+1]) 235 | } 236 | } 237 | 238 | func (r *Real) unpack(d []float64) { 239 | cb := r.cBuf 240 | end := len(cb) 241 | var re, im float64 242 | var v complex128 243 | for i := 0; i < end; i++ { 244 | v = cb[i] 245 | re = real(v) 246 | im = imag(v) 247 | d[2*i] = re 248 | d[2*i+1] = im 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /fft/real_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math" 8 | "math/rand" 9 | "testing" 10 | ) 11 | 12 | func TestRealEquivCmplx(t *testing.T) { 13 | for _, N := range []int{64, 65} { 14 | testRealEquivCmplx(N, true, t) 15 | testRealEquivCmplx(N, false, t) 16 | } 17 | } 18 | 19 | func testRealEquivCmplx(N int, scale bool, t *testing.T) { 20 | df := make([]float64, N) 21 | rft := NewReal(N) 22 | ft := New(N) 23 | rft.Scale(scale) 24 | ft.Scale(scale) 25 | dc := ft.Win(nil) 26 | 27 | for i := range df { 28 | v := rand.Float64() 29 | df[i] = v 30 | dc[i] = complex(v, 0.) 31 | } 32 | 33 | hc := rft.Do(df) 34 | ft.Do(dc) 35 | for i := 0; i < hc.Len(); i++ { 36 | if err := cmplxCmpErr(hc.Cmplx(i), dc[i], 1e-10, nil); err != nil { 37 | t.Errorf("at %d got %f not %f\n", i, hc.Cmplx(i), dc[i]) 38 | } 39 | } 40 | } 41 | 42 | func TestRealInv(t *testing.T) { 43 | iters := 1 44 | for _, N := range []int{32, 33} { 45 | d := make([]float64, N) 46 | tmp := make([]float64, N) 47 | for i := 0; i < iters; i++ { 48 | rft := NewReal(N) 49 | for j := 0; j < N; j++ { 50 | d[j] = rand.Float64() 51 | tmp[j] = d[j] 52 | } 53 | sp := rft.Do(d) 54 | dd := rft.Inv(sp) 55 | for j, v := range dd { 56 | if math.Abs(v-tmp[j]) > 1e-10 { 57 | t.Errorf("iter %d idx %d got %f not %f\n", i, j, v, tmp[j]) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | func BenchmarkReal(b *testing.B) { 65 | b.StopTimer() 66 | N := 1024 67 | w := make([]float64, N) 68 | tr := NewReal(N) 69 | b.StartTimer() 70 | for i := 0; i < b.N; i++ { 71 | tr.Do(w) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /fft/revbin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | func revBin(i int, N uint) int { 7 | if i > (1 << (N + 1)) { 8 | panic("i too big") 9 | } 10 | r := 0 11 | N-- 12 | for i != 0 { 13 | r |= (i % 2) << N 14 | N-- 15 | i /= 2 16 | } 17 | return r 18 | } 19 | 20 | func revBinLim(i int) uint { 21 | N := uint(0) 22 | for i > (1 << N) { 23 | N++ 24 | } 25 | return N 26 | } 27 | 28 | func revBinPermute(d []complex128) { 29 | m := len(d) 30 | N := revBinLim(m) 31 | for i := range d { 32 | j := revBin(i, N) 33 | if j < i { 34 | d[i], d[j] = d[j], d[i] 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fft/revbin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math/rand" 8 | "testing" 9 | ) 10 | 11 | func TestRevBin(t *testing.T) { 12 | 13 | L := uint(11) 14 | for lim := uint(0); lim < L; lim++ { 15 | for i := 0; i < (1 << lim); i++ { 16 | r := revBin(i, lim) 17 | j := revBin(r, lim) 18 | if j != i { 19 | t.Errorf("revBin non symmetric: %d, %d\n", i, r) 20 | } 21 | //fmt.Printf("%s r %s\n", strconv.FormatInt(int64(i), 2), strconv.FormatInt(int64(r), 2)) 22 | } 23 | } 24 | } 25 | 26 | func testRevBinPermute(t *testing.T) { 27 | N := 128 28 | L := revBinLim(N) 29 | d := make([]complex128, N) 30 | for i := range d { 31 | d[i] = complex(rand.Float64(), rand.Float64()) 32 | } 33 | c := make([]complex128, N) 34 | copy(d, c) 35 | revBinPermute(d) 36 | for i := range d { 37 | if c[i] != d[revBin(i, L)] { 38 | t.Errorf("%d %d not reversed\n", i, revBin(i, L)) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /fft/s.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "fmt" 8 | "image" 9 | "image/color" 10 | "image/png" 11 | "math" 12 | "math/cmplx" 13 | "os" 14 | 15 | "github.com/zikichombo/dsp/mathutil/qitp" 16 | "github.com/zikichombo/sound/sample" 17 | ) 18 | 19 | // S provides convenience wrappers around a ft spectrum. 20 | type S struct { 21 | mags []float64 22 | phases []float64 23 | neg int 24 | min, max float64 25 | } 26 | 27 | // NewS creates a spectrum from the data in d. Once 28 | // NewS returns, d is free to be used or gc'd. 29 | func NewS(d []complex128) *S { 30 | s := NewSN(len(d)) 31 | s.min = math.Inf(1) 32 | s.max = math.Inf(-1) 33 | for i, c := range d { 34 | m, p := cmplx.Polar(c) 35 | s.mags[i] = m 36 | s.phases[i] = p 37 | if m < s.min { 38 | s.min = m 39 | } 40 | if m > s.max { 41 | s.max = m 42 | } 43 | } 44 | return s 45 | } 46 | 47 | // NewSN creates a new S for ft spectrum of size n. 48 | func NewSN(n int) *S { 49 | mem := make([]float64, n*2) 50 | mags := mem[:n] 51 | phases := mem[n:] 52 | neg := Ny(n) 53 | min, max := math.Inf(1), math.Inf(-1) 54 | return &S{mags: mags, phases: phases, neg: neg, min: min, max: max} 55 | } 56 | 57 | // NewSHalfComplex creates a new spectrum object from a 58 | // HalfComplex object. 59 | func NewSHalfComplex(hc HalfComplex) *S { 60 | s := NewSN(len(hc)) 61 | s.FromHalfComplex(hc) 62 | return s 63 | } 64 | 65 | // At returns the complex representing the spectrum value at 66 | // symmetric index i. 67 | func (s *S) At(i int) complex128 { 68 | j := s.at(i) 69 | return cmplx.Rect(s.mags[j], s.phases[j]) 70 | } 71 | 72 | // Mag returns the magnitude at symmetric index i. 73 | func (s *S) Mag(i int) float64 { 74 | return s.mags[s.at(i)] 75 | } 76 | 77 | // SetMag sets the magnitude at symmetric index i. 78 | func (s *S) SetMag(i int, m float64) { 79 | s.mags[s.at(i)] = m 80 | if m < s.min { 81 | s.min = m 82 | } 83 | if m > s.max { 84 | s.max = m 85 | } 86 | } 87 | 88 | // MagDb returns the magnitude in decibels 89 | func (s *S) MagDb(i int) float64 { 90 | v := s.Mag(i) 91 | if v == 0 { 92 | v += 1e-20 93 | } 94 | return 20 * math.Log10(v) 95 | } 96 | 97 | // Phase returns the phase at symmetric index i. 98 | func (s *S) Phase(i int) float64 { 99 | return s.phases[s.at(i)] 100 | } 101 | 102 | // SetPhase sets the phase at symmetric index i to p. 103 | func (s *S) SetPhase(i int, p float64) { 104 | s.phases[s.at(i)] = p 105 | } 106 | 107 | // Ny returns the index of the first bin at or above 108 | // the Nyquist from the index of the input from NewS(). 109 | func (s *S) Ny() int { 110 | return s.neg 111 | } 112 | 113 | // N returns the number of frequency bins in s. 114 | func (s *S) N() int { 115 | return len(s.mags) 116 | } 117 | 118 | // Power returns the total power of the spectrum, 119 | // the sum of squares of magnitudes. Power assumes 120 | // s represents real data. 121 | func (s *S) Power() float64 { 122 | ttl := 0.0 123 | for i := 0; i < s.neg; i++ { 124 | ttl += s.mags[i] * s.mags[i] 125 | } 126 | ttl *= 2 127 | return math.Sqrt(ttl) 128 | } 129 | 130 | // ItpQMag uses quadratic interpolation to find the magnitude 131 | // at index i. Linear interpolation is used if s.N() == 2. 132 | // ItpQMag panics if s.N() < 2. 133 | func (s *S) ItpQMag(f float64) float64 { 134 | return sample.FromDb(qitp.SliceMap(s.mags, f, sample.ToDb)) 135 | } 136 | 137 | // Peaks returns the indices of the non-negative frequency bins 138 | // which are higher than one of their two neighbors and not 139 | // less than either neighbor. If there is only one element, 140 | // that element is returned. Endpoints are treated as though 141 | // they are strictly higher than beyond the endpoint. 142 | func (s *S) Peaks() []int { 143 | return s.PeaksTo(nil) 144 | } 145 | 146 | // PeaksTo places the peaks in dst by appending and returns 147 | // the result. 148 | func (s *S) PeaksTo(dst []int) []int { 149 | dst = dst[:0] 150 | n := len(s.mags) 151 | if n == 0 { 152 | return dst 153 | } 154 | if n == 1 { 155 | return dst 156 | } 157 | if n == 2 { 158 | dst = append(dst, 1) 159 | return dst 160 | } 161 | 162 | m := Ny(n) 163 | l := 0.0 164 | c := s.mags[0] 165 | r := s.mags[1] 166 | j := 2 167 | for j < m { 168 | l, c, r = c, r, s.mags[j] 169 | if c >= l && c >= r && (c > l || c > r) { 170 | dst = append(dst, j-1) 171 | } 172 | j++ 173 | } 174 | if r >= c { 175 | dst = append(dst, m-1) 176 | } 177 | return dst 178 | } 179 | 180 | // PeakItpQ performs interpolation of spectrum peaks, giving 181 | // a floating point index, magnitude, and phase. To retrieve the 182 | // frequency at the index, FreqAt is available. Peaks 183 | // often can correspond to sinusoidal waves which are off-center 184 | // of the frequency bin. The peak shape is modelled as a parabola 185 | // from neighboring points. 186 | // 187 | // If i is <= 1 or at the end of the Nyquist limit, then no 188 | // interpolation takes place and the bin information is returned. 189 | func (s *S) PeakItpQ(i int) (idx float64, mag float64, phase float64) { 190 | if i <= 1 || i >= s.neg-2 { 191 | return float64(i), s.mags[i], s.phases[i] 192 | } 193 | l, c, r := s.mags[i-1], s.mags[i], s.mags[i+1] 194 | l = sample.ToDb(l) 195 | c = sample.ToDb(c) 196 | r = sample.ToDb(r) 197 | a, b, c := qitp.Abc(l, c, r) 198 | h, k := qitp.Abc2Hk(a, b, c) 199 | mag = sample.FromDb(k) 200 | idx = float64(i) + h 201 | phase = 0.0 202 | return 203 | } 204 | 205 | // ItpPeaks interpolates all the peaks in the spectrum, returning their 206 | // interpolated indices, magnitudes and phases. Quadratic interpolation on log 207 | // scale magnitudes is used, as in PeakItpQ, but the returned magnitudes are 208 | // not log scale. 209 | // 210 | // The returned slice contains interpolated indices at n*3 positions and 211 | // interpolated magnitudes at n*3+1 positions, and interpolated phases at n*3+2 212 | // position. 213 | // 214 | // For example, if there are two peaks at 1.23 and 30.91 with magnitudes 10 and 215 | // 100, and phases pi/49, 8pi/9 then the returned slice would be 216 | // 217 | // {1.23, 10, pi/49, 30.91, 100, 8pi/9} 218 | func (s *S) ItpPeaks(dst []float64) []float64 { 219 | ps := s.Peaks() 220 | for _, p := range ps { 221 | i, m, p := s.PeakItpQ(p) 222 | dst = append(dst, i) 223 | dst = append(dst, m) 224 | dst = append(dst, p) 225 | } 226 | return dst 227 | } 228 | 229 | // FromRect resets s to use the complex spectrum d. 230 | func (s *S) FromRect(d []complex128) error { 231 | if len(d) != len(s.mags) { 232 | return fmt.Errorf("mismatched spectrum lengths: %d != %d", len(d), len(s.mags)) 233 | } 234 | s.min, s.max = math.Inf(1), math.Inf(-1) 235 | for i, c := range d { 236 | m, p := cmplx.Polar(c) 237 | s.mags[i] = m 238 | s.phases[i] = p 239 | if m < s.min { 240 | s.min = m 241 | } 242 | if m > s.max { 243 | s.max = m 244 | } 245 | } 246 | return nil 247 | } 248 | 249 | // FromHalfComplex makes s contain spectrum from hc. 250 | // 251 | // FromHalfComplex returns a non-nil error if 252 | // s doesn't contain the same number of elements as hc. 253 | func (s *S) FromHalfComplex(hc HalfComplex) error { 254 | if len(hc) != len(s.mags) { 255 | return fmt.Errorf("mismatched spectrum lengths: %d != %d", len(hc), len(s.mags)) 256 | } 257 | if len(hc) == 0 { 258 | return nil 259 | } 260 | hc.ToPolar(s.mags, s.phases) 261 | return nil 262 | } 263 | 264 | // Rect puts the spectrum in rectangular complex (real + imag) form in dst. 265 | // If dst doesn't have capacity for the data, then a new slice is allocated 266 | // and returned. Otherwise, the results are placed in dst and returned. 267 | func (s *S) Rect(dst []complex128) []complex128 { 268 | if cap(dst) < len(s.mags) { 269 | dst = make([]complex128, len(s.mags)) 270 | } 271 | dst = dst[:len(s.mags)] 272 | for i := range dst { 273 | dst[i] = cmplx.Rect(s.mags[i], s.phases[i]) 274 | } 275 | return dst 276 | } 277 | 278 | // ToHalfComplex places the spectrum s in dst. 279 | // 280 | // If dst doesn't have capacity for the data, then a new slice 281 | // is allocated and returned. Otherwise, the results are placed in dst 282 | // and returned. 283 | func (s *S) ToHalfComplex(dst HalfComplex) HalfComplex { 284 | if cap(dst) != len(s.mags) { 285 | dst = HalfComplex(make([]float64, len(s.mags))) 286 | } 287 | dst = dst[:len(s.mags)] 288 | dst.FromPolar(s.mags, s.phases) 289 | return dst 290 | } 291 | 292 | func (s *S) PlotMagTo(b image.Rectangle, p string) error { 293 | f, e := os.Create(p) 294 | if e != nil { 295 | return e 296 | } 297 | defer f.Close() 298 | img := s.PlotMag(b) 299 | return png.Encode(f, img) 300 | } 301 | 302 | // PlotMag plots the magnitudes on an image of 303 | // dimensions b and returns it. 304 | func (s *S) PlotMag(b image.Rectangle) *image.RGBA { 305 | mAcc := 0.0 306 | wAcc := 0.0 307 | rAcc := 0.0 308 | im := image.NewRGBA(b) 309 | x := 0 310 | black := color.RGBA{A: 255} 311 | for x := b.Min.X; x < b.Max.X; x++ { 312 | im.Set(x, b.Min.Y, black) 313 | im.Set(x, b.Max.Y-1, black) 314 | } 315 | for y := b.Min.Y; y < b.Max.Y; y++ { 316 | im.Set(b.Min.X, y, black) 317 | im.Set(b.Max.X-1, y, black) 318 | } 319 | bb := image.Rect(b.Min.X+1, b.Min.Y+1, b.Max.X-1, b.Max.Y-1) 320 | subIm := im.SubImage(bb).(*image.RGBA) 321 | r := float64(bb.Dx()) / float64(len(s.mags)) 322 | 323 | color := color.RGBA{ 324 | A: 180, 325 | R: 0, 326 | G: 200, 327 | B: 255} 328 | if s.min == 0 { 329 | s.min = 1e-10 330 | } 331 | minDb := sample.ToDb(s.min) 332 | maxDb := sample.ToDb(s.max) 333 | for j := 0; j < len(s.mags); j++ { 334 | i := j - s.neg 335 | if i < 0 { 336 | i += len(s.mags) 337 | } 338 | mdb := s.MagDb(i) 339 | rAcc += r 340 | if rAcc < 1.0 { 341 | mAcc += mdb * r 342 | wAcc += r 343 | continue 344 | } 345 | _, ar := math.Modf(rAcc) 346 | mAcc += mdb * (r - ar) 347 | wAcc += r - ar 348 | v := mAcc / wAcc 349 | yr := (v - minDb) / (maxDb - minDb) 350 | Y := int(math.Floor(yr*float64(bb.Dy()) + 0.5)) 351 | for rAcc >= 1.0 { 352 | for y := 1; y <= Y; y++ { 353 | subIm.Set(b.Min.X+x, b.Max.Y-y, color) 354 | } 355 | rAcc -= 1.0 356 | x++ 357 | } 358 | mAcc = mdb * ar 359 | rAcc = ar 360 | wAcc = ar 361 | } 362 | return im 363 | } 364 | 365 | // FoldReal takes the spectrum and makes all negative frequency bins 366 | // complex conjugates of the corresponding positive frequency bin to 367 | // guarantee real output of inverse fft. 368 | // 369 | func (s *S) FoldReal() { 370 | N := s.Ny() 371 | M := len(s.phases) 372 | s.phases[0] = 0 373 | if M%2 == 0 { 374 | s.phases[N] = 0 375 | } 376 | for i := 1; i < N; i++ { 377 | s.phases[M-i] = -s.phases[i] 378 | s.mags[M-i] = s.mags[i] 379 | } 380 | } 381 | 382 | // CopyFrom makes s a copy of t. 383 | func (s *S) CopyFrom(t *S) { 384 | if cap(s.mags) < len(t.mags) { 385 | s.mags = make([]float64, len(t.mags)) 386 | s.phases = make([]float64, len(t.phases)) 387 | } 388 | s.mags = s.mags[:len(t.mags)] 389 | s.phases = s.phases[:len(t.mags)] 390 | copy(s.mags, t.mags) 391 | copy(s.phases, t.phases) 392 | } 393 | 394 | func (s *S) at(i int) int { 395 | if i < 0 { 396 | return len(s.mags) + i 397 | } 398 | return i 399 | } 400 | -------------------------------------------------------------------------------- /fft/s_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "fmt" 8 | "image" 9 | "image/png" 10 | "math" 11 | "math/rand" 12 | "os" 13 | "testing" 14 | 15 | "github.com/zikichombo/sound/freq" 16 | ) 17 | 18 | func TestPeaks(t *testing.T) { 19 | N := 512 20 | d := generate(5000*freq.Hertz, N) 21 | Do(d) 22 | sp := NewS(d) 23 | peaks := sp.Peaks() 24 | for _, p := range peaks { 25 | i, m, p := sp.PeakItpQ(p) 26 | fmt.Printf("peak at %f: |%.2f| <%.2f>\n", i, m, p) 27 | } 28 | if len(peaks) != 3 { 29 | t.Errorf("wrong number of peaks: %d != 3\n", len(peaks)) 30 | } 31 | } 32 | 33 | func TestFoldReal(t *testing.T) { 34 | for i := 0; i < 4; i++ { 35 | n := rand.Intn(100) + 10 36 | sp := NewSN(n) 37 | for j := 0; j < n; j++ { 38 | sp.SetMag(j, rand.Float64()) 39 | sp.SetPhase(j, rand.Float64()) 40 | } 41 | //sp.SetPhase(sp.Ny(), 0.0) 42 | sp.FoldReal() 43 | d := sp.Rect(nil) 44 | Do(d) 45 | for i, v := range d { 46 | if math.Abs(imag(v)) > 0.0000000001 { 47 | t.Errorf("%d: fold real n=%d gave imag component ift: %f\n", i, n, v) 48 | } 49 | } 50 | } 51 | } 52 | 53 | func TestSPlot(t *testing.T) { 54 | N := 512 55 | d := make([]complex128, N) 56 | for i := range d { 57 | d[i] = complex(math.Sin(float64(i)*15000.0*2*math.Pi/44100.0), 0) 58 | } 59 | Do(d) 60 | sp := NewS(d) 61 | im := sp.PlotMag(image.Rect(0, 0, 640, 480)) 62 | f, e := os.Create("spect.png") 63 | if e != nil { 64 | t.Fatal(e) 65 | } 66 | if e := png.Encode(f, im); e != nil { 67 | t.Fatal(e) 68 | } 69 | } 70 | 71 | /* 72 | func TestSPlot(t *testing.T) { 73 | N := 512 74 | d := generate(5000*freq.Hertz, N) 75 | Do(d) 76 | sp := NewS(d) 77 | p := sp.PlotMag(8 * vg.Inch) 78 | fmt := "png" 79 | f, e := os.Create("spex.png") 80 | if e != nil { 81 | t.Fatal(e) 82 | } 83 | defer f.Close() 84 | wt, e := p.WriterTo(8*vg.Inch, 8*vg.Inch, fmt) 85 | if e != nil { 86 | t.Fatal(e) 87 | } 88 | if _, e := wt.WriteTo(f); e != nil { 89 | t.Fatal(e) 90 | } 91 | } 92 | */ 93 | 94 | func TestSSymEvenN(t *testing.T) { 95 | testSSymN(16, t) 96 | } 97 | 98 | func TestSSymOddN(t *testing.T) { 99 | testSSymN(17, t) 100 | } 101 | 102 | func testSSymN(N int, t *testing.T) { 103 | ft := New(N) 104 | b := ft.Win(nil) 105 | for i := 0; i < 10; i++ { 106 | for j := 0; j < N; j++ { 107 | c := complex(rand.Float64(), 0) 108 | b[j] = c 109 | } 110 | if e := ft.Do(b); e != nil { 111 | t.Fatal(e) 112 | } 113 | s := NewS(b) 114 | ny := s.Ny() 115 | for j := 0; j < ny; j++ { 116 | p, n := s.Mag(j), s.Mag(-j) 117 | if math.Abs(p-n) > 0.00000000001 { 118 | t.Errorf("at +/- %d %f, %f\n", j, p, n) 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /fft/scale.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import "math" 7 | 8 | // this scaling function, applied to forward and inverse 9 | // transforms makes the fwd and inv reciprocal and 10 | // makes parsevals equation hold (sum of squares of 11 | // input is equal to sum of squares of transform) 12 | // 13 | // this latter condition facilitates using this package 14 | // in a way where frequency domain values are proportional 15 | // to input values. 16 | // 17 | func scale(d []complex128) { 18 | n := len(d) 19 | if n <= 1 { 20 | return 21 | } 22 | m := 1 / math.Sqrt(float64(len(d))) 23 | a := complex(m, 0) 24 | for i := range d { 25 | d[i] *= a 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fft/t.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "fmt" 8 | "math/cmplx" 9 | ) 10 | 11 | // T maintains state for efficient repeated computation on windows of data. 12 | type T struct { 13 | n, padN int // padN = n if n is power of 2, least power of 2 >= 2*n + 1 otherwise. 14 | twids *twiddles // size padN 15 | chirpz *chirpz // nil if n == padN 16 | itwids *twiddles 17 | ichirpz *chirpz 18 | scale bool 19 | } 20 | 21 | // New creates a new T which can compute repeated windows more efficiently than 22 | // repeatedly calling Do or To 23 | // 24 | // - n is the size of the data windows to transform 25 | // 26 | // - inv is whether or not to use the "inverse" transform 27 | func New(n int) *T { 28 | if is2pow(n) { 29 | return NewT(n, n) 30 | } 31 | return NewT(n, 1< 0.00005 { 214 | //fmt.Printf("\t%d: [%s .. %s) |%.2f| <%.2f>\n", i, l, u, mag, p) 215 | _, _, _ = l, u, p 216 | } 217 | } 218 | //fmt.Println() 219 | } 220 | 221 | func cmplxCmpErr(a, b complex128, eps float64, t *testing.T) error { 222 | var e error 223 | if cmplx.Abs(a-b) > eps || real(a) == math.NaN() || real(b) == math.NaN() { 224 | e = fmt.Errorf("got %f expecting %f", a, b) 225 | if t != nil { 226 | t.Error(e) 227 | } 228 | } 229 | return e 230 | } 231 | -------------------------------------------------------------------------------- /fft/twiddle.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "strings" 10 | ) 11 | 12 | // implementation of twiddle caching for FFT algorithms 13 | 14 | // Reminder: e^{ix} = cos(x) + i sin(x), i == sqrt(-1) 15 | // and e^{-ix} = cos(x) - i sin(x) 16 | // 17 | // so e^{ix} is complex conjugate of e^{-ix} 18 | // 19 | 20 | type twiddles struct { 21 | cosTbl []float64 22 | sinTbl []float64 23 | twoPi int 24 | inv bool 25 | invSin float64 26 | } 27 | 28 | func newTwiddles(n int, inv bool) *twiddles { 29 | tbl := make([]float64, 2*n) 30 | res := &twiddles{ 31 | cosTbl: tbl[:n], 32 | sinTbl: tbl[n:], 33 | twoPi: n} 34 | w := 2.0 * math.Pi / float64(n) 35 | for i := 0; i < n; i++ { 36 | s, c := math.Sincos(float64(i) * w) 37 | res.cosTbl[i] = c 38 | res.sinTbl[i] = s 39 | } 40 | res.inv = inv 41 | if !inv { 42 | res.invSin = -1.0 43 | } else { 44 | res.invSin = 1.0 45 | } 46 | return res 47 | } 48 | 49 | func (t *twiddles) sincos(i int) (float64, float64) { 50 | return t.sin(i), t.cos(i) 51 | } 52 | 53 | func (t *twiddles) sincosQ(i, q int) (float64, float64) { 54 | j := i * t.twoPi / q 55 | return t.sin(j), t.cos(j) 56 | } 57 | 58 | func (t *twiddles) cmplx(i int) complex128 { 59 | im, re := t.sincos(i) 60 | return complex(re, im) 61 | } 62 | 63 | func (t *twiddles) cmplxQ(i, q int) complex128 { 64 | j := i * t.twoPi / q 65 | return t.cmplx(j) 66 | } 67 | 68 | func (t *twiddles) sin(i int) float64 { 69 | return t.invSin * t.sinTbl[i%t.twoPi] 70 | } 71 | 72 | // nb this is a hot spot on profiling. 73 | func (t *twiddles) cos(i int) float64 { 74 | return t.cosTbl[i%t.twoPi] 75 | } 76 | 77 | func (t *twiddles) String() string { 78 | res := make([]string, 0, len(t.cosTbl)) 79 | for _, d := range t.cosTbl { 80 | res = append(res, fmt.Sprintf("%.2f", d)) 81 | } 82 | return strings.Join(res, " ") 83 | } 84 | -------------------------------------------------------------------------------- /fft/twiddle_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | import ( 7 | "math" 8 | "testing" 9 | ) 10 | 11 | func TestTwiddle(t *testing.T) { 12 | N := 1024 13 | tw := newTwiddles(1024, false) 14 | itw := newTwiddles(1024, true) 15 | eps := 1e-8 16 | for i := 0; i < N; i++ { 17 | s, c := tw.sincos(i) 18 | r := (float64(i) / float64(N)) * math.Pi * 2 19 | ms, mc := math.Sin(r), math.Cos(r) 20 | es, ec := math.Abs(s+ms), math.Abs(c-mc) 21 | if es > eps { 22 | t.Errorf("sin error too large: %f", es) 23 | } 24 | if ec > eps { 25 | t.Errorf("cos error too large: %f", ec) 26 | } 27 | twid := tw.cmplx(i) 28 | itwid := itw.cmplx(i) 29 | if math.Abs(imag(twid)+imag(itwid)) > eps { 30 | t.Errorf("imag doesn't add to 0") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /fft/zpad.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package fft 5 | 6 | // ZeroPad returns a zero-padding of the Fourier Transform d for time interpolation. 7 | // If we divide a transform into a 0(dc) component, components not exceeding the 8 | // Nyquist limit, and higher frequencies as "negative" frequencies, then the zero 9 | // padding goes in-between the non-negative frequencies and the negative frequencies. 10 | // The inverse FT will then generate time-interpolation of the data for which 11 | // d is a FT. 12 | func ZeroPad(d []complex128, n int) []complex128 { 13 | return ZeroPadTo(nil, d, n) 14 | } 15 | 16 | // ZeroPadTo is like ZeroPad but allows specifying a destination vector. 17 | // If dst doesn't have sufficient capacity, then a new one is created 18 | // and returned. 19 | func ZeroPadTo(dst, src []complex128, n int) []complex128 { 20 | if cap(dst) < len(src)+n { 21 | dst = make([]complex128, len(src)+n) 22 | } else { 23 | dst = dst[:len(src)+n] 24 | } 25 | l := len(src) 26 | m := l / 2 27 | if n%2 == 0 { 28 | m++ 29 | } 30 | for i := l; i < l+n; i++ { 31 | dst[i] = 0i 32 | } 33 | copy(dst[:m], src[:m]) 34 | copy(dst[m+n:], src[m:]) 35 | return dst 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zikichombo/dsp 2 | 3 | go 1.17 4 | 5 | require github.com/zikichombo/sound v0.2.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/zikichombo/sound v0.2.1 h1:QkrADAG+59C/iyB2bu4FWrq3hhnep6geYVjWElelFHs= 2 | github.com/zikichombo/sound v0.2.1/go.mod h1:5hxLHw/ZMixN32dsks+21p6WBAI81qKpSBSkLvYLmPU= 3 | -------------------------------------------------------------------------------- /lpc/README.md: -------------------------------------------------------------------------------- 1 | # Linear Predictive Coding 2 | 3 | Linear predictive coding is a standard method for modelling 4 | signals. This package provides a basic implementation based 5 | on the autocorrelation method with filter stability enforcement. 6 | 7 | 8 | -------------------------------------------------------------------------------- /lpc/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Package lpc provides a linear predictive coding interface. 5 | // 6 | // The LPC modelling algorithm is based on the autocorrelation method with the 7 | // addition of a numerical tweak to enforce stability of the resulting model. 8 | // 9 | // Package lpc supports modelling, predicting and generation/synthesis. 10 | // 11 | // Package lpc does not yet support line spectral frequencies or 12 | // conversion to other coefficient representations. 13 | package lpc 14 | -------------------------------------------------------------------------------- /lpc/state.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Copyright 2018 Iri France SAS. All rights reserved. Use of this source code 5 | // is governed by a license that can be found in the License file. 6 | 7 | package lpc 8 | 9 | // State encapsulates linear prediction state for 10 | // incremental usage in synthesis and prediction. 11 | type State struct { 12 | alpha []float64 13 | hist []float64 14 | i int 15 | } 16 | 17 | // Predict returns the current prediction for the 18 | // next value. 19 | func (s *State) Predict() float64 { 20 | n := len(s.hist) 21 | k := 0 22 | i := s.i 23 | ttl := 0.0 24 | for j := 0; j < n; j++ { 25 | k = (j + i) % n 26 | ttl += s.hist[k] * s.alpha[j] 27 | } 28 | return ttl 29 | } 30 | 31 | // Consume advances the state one element (d), and 32 | // returns the residue of the model for d. 33 | func (s *State) Consume(d float64) float64 { 34 | p := s.Predict() 35 | s.hist[s.i] = d 36 | s.i++ 37 | if s.i == len(s.hist) { 38 | s.i = 0 39 | } 40 | return d - p 41 | } 42 | 43 | // Produce synthesizes the next state from the residue r. 44 | func (s *State) Produce(r float64) float64 { 45 | m := s.Predict() 46 | v := r + m 47 | s.Consume(v) 48 | return v 49 | } 50 | -------------------------------------------------------------------------------- /lpc/t.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package lpc 5 | 6 | import ( 7 | "log" 8 | "math" 9 | ) 10 | 11 | const eps = 1e-12 12 | 13 | // T holds states for doing linear predictive coding of a given order. 14 | type T struct { 15 | rs []float64 16 | k []float64 17 | alpha []float64 18 | } 19 | 20 | // New returns a new linear predictive coder. 21 | func New(order int) *T { 22 | return &T{ 23 | rs: make([]float64, order+1), 24 | k: make([]float64, order+1), 25 | alpha: make([]float64, order+1)} 26 | } 27 | 28 | // Order returns the order of p. 29 | func (p *T) Order() int { 30 | return len(p.rs) - 1 31 | } 32 | 33 | // Model causes p to learn coeficients for d, returning 34 | // the model error. 35 | func (p *T) Model(d []float64) float64 { 36 | err := p.levDurb(d) 37 | return err 38 | } 39 | 40 | // State returns an lpc state for incrementally predicting 41 | // and synthesizing values according to the model in p. 42 | func (p *T) State(seed []float64) *State { 43 | order := p.Order() 44 | st := &State{ 45 | hist: make([]float64, order), 46 | alpha: make([]float64, order)} 47 | copy(st.hist, seed) 48 | copy(st.alpha, p.alpha[1:]) 49 | half := order / 2 50 | end := len(st.alpha) - 1 51 | for i := 0; i < half; i++ { 52 | st.alpha[i], st.alpha[end-i] = st.alpha[end-i], st.alpha[i] 53 | } 54 | return st 55 | } 56 | 57 | // Residue applies the model to d and for d[p.Order():] 58 | // replaces the value of the input with the error (aka residue) 59 | // of the model. 60 | func (p *T) Residue(d []float64) { 61 | order := p.Order() 62 | for i := len(d) - 1; i >= order; i-- { 63 | iModel := 0.0 64 | for o := 1; o <= order; o++ { 65 | iModel += p.alpha[o] * d[i-o] 66 | } 67 | d[i] -= iModel 68 | } 69 | } 70 | 71 | // Restore restores d if d[p.Order():] is a residue generated 72 | // from d[:p.Order()]. 73 | func (p *T) Restore(d []float64) { 74 | order := p.Order() 75 | N := len(d) 76 | for i := order; i < N; i++ { 77 | acc := 0.0 78 | for o := 1; o <= order; o++ { 79 | acc += p.alpha[o] * d[i-o] 80 | } 81 | d[i] += acc 82 | } 83 | } 84 | 85 | // R0 returns the zero autocorrelation value, useful 86 | // for normalizing error. 87 | func (p *T) R0() float64 { 88 | return p.rs[0] 89 | } 90 | 91 | func (p *T) ld2(d []float64) float64 { 92 | p.autoCorr(d) 93 | err := p.rs[0] 94 | r := 0.0 95 | order := p.Order() 96 | for i := 1; i <= order; i++ { 97 | r = -p.rs[i] 98 | for j := 1; j < i; j++ { 99 | r -= p.alpha[j] * p.rs[i-j] 100 | } 101 | r /= err 102 | p.alpha[i] = r 103 | err *= (1.0 - r*r) 104 | for j := 1; j < i/2; j++ { 105 | t := p.alpha[j] 106 | p.alpha[j] += r * p.alpha[i-j] 107 | p.alpha[i-j] += r * t 108 | } 109 | if i%2 == 1 { 110 | p.alpha[i/2] += p.alpha[i/2] * r 111 | } 112 | if err == 0.0 { 113 | log.Printf("need to limit order...") 114 | } 115 | } 116 | for i := 1; i <= order; i++ { 117 | p.alpha[i] = -p.alpha[i] 118 | } 119 | return err 120 | } 121 | 122 | func (p *T) levDurb(d []float64) float64 { 123 | p.autoCorr(d) 124 | err := p.rs[0] 125 | if math.Abs(err) < eps { 126 | err = 1.0 / eps 127 | } 128 | order := p.Order() 129 | alphaTmp := make([]float64, len(p.alpha)) 130 | i := 1 131 | for i <= order { 132 | k := p.rs[i] 133 | for j := 1; j < i; j++ { 134 | fub := p.alpha[j] * p.rs[i-j] 135 | k -= fub 136 | } 137 | k /= err 138 | if math.Abs(k) > 1.0 { 139 | k = 1.0 / k 140 | } 141 | alphaTmp[i] = k 142 | for j := 1; j < i; j++ { 143 | alphaTmp[j] -= k * p.alpha[i-j] 144 | } 145 | copy(p.alpha, alphaTmp[:i+1]) 146 | err *= (1.0 - k*k) 147 | i++ 148 | if math.Abs(err) < eps { 149 | break 150 | } 151 | } 152 | p.rs = p.rs[:i] 153 | return err 154 | } 155 | 156 | func (p *T) autoCorr(d []float64) { 157 | N := len(d) - p.Order() 158 | for i := 0; i < N; i++ { 159 | u := d[i] 160 | for j := 0; j < len(p.rs); j++ { 161 | v := d[i+j] 162 | p.rs[j] += u * v 163 | } 164 | } 165 | for i := range p.rs { 166 | p.rs[i] /= float64(N) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lpc/t_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package lpc 5 | 6 | import ( 7 | "math" 8 | "testing" 9 | 10 | "github.com/zikichombo/sound/freq" 11 | "github.com/zikichombo/sound/gen" 12 | ) 13 | 14 | func TestLpc(t *testing.T) { 15 | testLpcConst(16, 1, t) 16 | testLpcConst(16, 2, t) 17 | testLpcConst(16, 3, t) 18 | for _, i := range []int{1, 2, 3, 4, 5, 6, 7, 8} { 19 | testLpcSin(512, i, t) 20 | } 21 | } 22 | 23 | func testLpcConst(N, order int, t *testing.T) { 24 | lpc := New(order) 25 | d := make([]float64, N) 26 | for i := range d { 27 | d[i] = 103.0 28 | } 29 | lpc.Model(d) 30 | lpc.Residue(d) 31 | for i := order; i < len(d); i++ { 32 | if d[i] != 0.0 { 33 | t.Errorf("lpc constant data[%d] prediction order %d failed at %d: gave %f wanted %f\n", N, order, i, d[i], 0.0) 34 | } 35 | } 36 | lpc.Restore(d) 37 | for i, v := range d { 38 | if v != 103.0 { 39 | t.Errorf("%d: residue/restore got %f not 103", i, v) 40 | } 41 | } 42 | } 43 | 44 | func testLpcSin(N, order int, t *testing.T) { 45 | lpc := New(order) 46 | //src, _ := ops.Add(ops.Amplify(gen.Noise(), 0.001), ops.Amplify(gen.Note(440*freq.Hertz), 0.999)) 47 | src := gen.Note(440 * freq.Hertz) 48 | d := make([]float64, N) 49 | e := make([]float64, N) 50 | src.Receive(d) 51 | copy(e, d) 52 | lpc.Model(d) 53 | lpc.Residue(d) 54 | consumer := lpc.State(d[:order]) 55 | producer := lpc.State(d[:order]) 56 | for i := order; i < N; i++ { 57 | r := consumer.Consume(e[i]) 58 | v := producer.Produce(r) 59 | if math.Abs(r-d[i]) > 1e-3 { 60 | t.Errorf("N=%d, order %d, at %d residue mismatch: got %f wanted %f\n", N, order, i, r, e[i]) 61 | } 62 | if math.Abs(v-e[i]) > 1e-3 { 63 | t.Errorf("N=%d, order %d, at %d produce from residue mismatch got %f wanted %f\n", N, order, i, v, d[i]) 64 | } 65 | } 66 | lpc.Restore(d) 67 | for i, v := range d { 68 | if math.Abs(v-e[i]) > 1e-3 { 69 | t.Errorf("at %d lpc restore got %f not %f\n", i, e[i], v) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mathutil/qitp/abc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package qitp 5 | 6 | import "math" 7 | 8 | // Abc takes three y values denoting points (-1, p), (0, q), (1, r) 9 | // and returns the coeficients a, b, c such that 10 | // 11 | // y = f(x) = ax^2 + bx + c 12 | func Abc(p, q, r float64) (a, b, c float64) { 13 | // c = q by assumption of x values of p,q,r. 14 | c = q 15 | // 1) r = a + b + q <=> 1a) r - q = a + b 16 | // 2) p = a - b + q <=> 2a) p - q = a - b 17 | // 18 | // add 1a) + 2a) gives r + p - 2*q = 2*a 19 | a = 0.5*(r+p) - q 20 | 21 | // subst a in 1) 22 | // r = a + b + q <=> b = r - a - q 23 | // rearrange to p - 2q + b + q = 0 <=> b = q - p 24 | b = r - a - q 25 | return 26 | } 27 | 28 | // Abc2Hk translates standard to vertex form y = a(x - h)^2 + k 29 | func Abc2Hk(a, b, c float64) (h, k float64) { 30 | h = -b / (2 * a) 31 | k = a*h*h + b*h + c 32 | return 33 | } 34 | 35 | func Ahk2Bc(a, h, k float64) (b, c float64) { 36 | b = -2 * h * a 37 | c = a*h*h + k 38 | return 39 | } 40 | 41 | // AbcX returns the point interpolated with x in [-1,1] 42 | func AbcX(p, q, r, x float64) float64 { 43 | if x < -1+1e-10 || x > 1-1e-10 { 44 | panic("x oob") 45 | } 46 | a, b, c := Abc(p, q, r) 47 | return a*x*x + b*x + c 48 | } 49 | 50 | // ParabX0 returns a function which can be queried for 51 | // values in the range [-1,1] returning interpolated values 52 | // from p,q,r 53 | func ParabX0(p, q, r float64) func(float64) float64 { 54 | a, b, c := Abc(p, q, r) 55 | return func(x float64) float64 { 56 | return a*x*x + b*x + c 57 | } 58 | } 59 | 60 | // Slice interpolates the index x in vs quadratically 61 | // if possible and linearly if len(vs) == 2. 62 | // 63 | // Slice panics if 64 | // 65 | // - len(vs) <= 1 66 | // 67 | // - x is out of bounds 68 | func Slice(vs []float64, x float64) float64 { 69 | return SliceMap(vs, x, func(v float64) float64 { return v }) 70 | } 71 | 72 | // SliceMap is like slice but maps the values in vs using m(v) 73 | // before interpolation. The returned value is by extension also mapped. 74 | // 75 | // Mapping is provided since the hard part is finding the indices. 76 | func SliceMap(vs []float64, x float64, m func(float64) float64) float64 { 77 | if len(vs) <= 1 { 78 | panic("not big nuf") 79 | } 80 | xi, xf := math.Modf(x) 81 | c := int(xi) 82 | if c < 0 || c >= len(vs) || (c == len(vs)-1 && xf > 1e-10) { 83 | panic("oob") 84 | } 85 | if c == len(vs)-1 { 86 | return m(vs[c]) 87 | } 88 | if len(vs) == 2 { // back of to linear. 89 | return (1-xf)*m(vs[0]) + xf*m(vs[1]) 90 | } 91 | if xf < 1e-10 { 92 | return m(vs[c]) 93 | } 94 | if 1-xf < 1e-10 { 95 | return m(vs[c+1]) 96 | } 97 | r := c + 1 98 | l := c - 1 99 | if r+1 < len(vs) && (l < 0 || xf >= 0.5) { 100 | l, c, r = c, r, r+1 101 | // x is in [l..c) 102 | return AbcX(m(vs[l]), m(vs[c]), m(vs[r]), -1+xf) 103 | } 104 | // x is in (c,r) 105 | return AbcX(m(vs[l]), m(vs[c]), m(vs[r]), xf) 106 | } 107 | -------------------------------------------------------------------------------- /mathutil/qitp/abc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package qitp_test 5 | 6 | import ( 7 | "math" 8 | "math/rand" 9 | "testing" 10 | 11 | "github.com/zikichombo/dsp/mathutil/qitp" 12 | ) 13 | 14 | func TestAbc(t *testing.T) { 15 | for i := 0; i < 100; i++ { 16 | p, q, r := rand.Float64(), rand.Float64(), rand.Float64() 17 | a, b, c := qitp.Abc(p, q, r) 18 | if math.Abs(p-(a*-1.0*-1.0+b*-1.0+c)) > 1e-10 { 19 | t.Errorf("p didn't interpolate: Abc(%f,%f,%f) = (%f,%f,%f)", p, q, r, a, b, c) 20 | } 21 | if math.Abs(q-(a*0*0+b*0+c)) > 1e-10 { 22 | t.Errorf("q didn't interpolate: Abc(%f,%f,%f) = (%f,%f,%f)", p, q, r, a, b, c) 23 | } 24 | if math.Abs(r-(a*1.0*1.0+b*1.0+c)) > 1e-10 { 25 | t.Errorf("r didn't interpolate: Abc(%f,%f,%f) = (%f,%f,%f)", p, q, r, a, b, c) 26 | } 27 | } 28 | } 29 | 30 | func TestSlice2(t *testing.T) { 31 | sl := make([]float64, 2) 32 | defer func() { 33 | if e := recover(); e != nil { 34 | t.Error(e) 35 | } 36 | }() 37 | for i := 0; i < 16; i++ { 38 | sl[0] = rand.Float64() 39 | sl[1] = rand.Float64() 40 | x := rand.Float64() 41 | qitp.Slice(sl, x) 42 | } 43 | } 44 | 45 | func TestAbcV(t *testing.T) { 46 | for i := 0; i < 16; i++ { 47 | a, b, c := rand.Float64(), rand.Float64(), rand.Float64() 48 | h, k := qitp.Abc2Hk(a, b, c) 49 | for i := 0; i < 10; i++ { 50 | x := 2 * (rand.Float64() - 0.5) 51 | u := a*x*x + b*x + c 52 | v := a*(x-h)*(x-h) + k 53 | if math.Abs(u-v) > 1e-10 { 54 | t.Errorf("abchk") 55 | } 56 | } 57 | } 58 | } 59 | 60 | func TestSliceN(t *testing.T) { 61 | defer func() { 62 | if e := recover(); e != nil { 63 | t.Error(e) 64 | } 65 | }() 66 | for i := 0; i < 16; i++ { 67 | n := rand.Intn(10) + 2 68 | fn := float64(n - 1) 69 | 70 | sl := make([]float64, n) 71 | for i := range sl { 72 | sl[i] = rand.Float64() 73 | } 74 | for j := 0; j < n; j++ { 75 | x := rand.Float64() * fn 76 | qitp.Slice(sl, x) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /mathutil/qitp/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package qitp 5 | -------------------------------------------------------------------------------- /resample/ct.go: -------------------------------------------------------------------------------- 1 | package resample 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "math" 7 | 8 | "github.com/zikichombo/dsp/wfn" 9 | "github.com/zikichombo/sound" 10 | "github.com/zikichombo/sound/cil" 11 | "github.com/zikichombo/sound/freq" 12 | ) 13 | 14 | const ( 15 | shiftSize = 64 16 | ) 17 | 18 | // Type C holds state for giving a continuous time 19 | // representation of a sound.Source. 20 | type C struct { 21 | src sound.Source 22 | shift int 23 | bufSize int 24 | cbufs [][]float64 25 | rbuf []float64 26 | off int 27 | err error 28 | eps float64 29 | itper Itper 30 | } 31 | 32 | // SampleRateConverter provides an interface to a dynamic resample rate 33 | // conversion. 34 | type SampleRateConverter interface { 35 | // Convert is called by the resampling methods in this package to determine 36 | // the result frequency in a conversion. 37 | // 38 | // It is called once for every output sample except the first sample, which 39 | // is taken to be at the same point in time as the first input sample. 40 | // 41 | // The return value should provide the ratio of the output rate to the input 42 | // rate. It is assumed the input rate is fixed and determined by calling 43 | // context. 44 | Convert() float64 45 | } 46 | 47 | // ConstSampleRateStretcher is a type which implements SampleRateConverter 48 | // based on a float64 constant sample rate conversion ratio. 49 | type constSampleRateConverter float64 50 | 51 | // Stretch implements SampleRateStretcher 52 | func (c constSampleRateConverter) Convert() float64 { 53 | return float64(c) 54 | } 55 | 56 | // DynResampler is used to dynamically resample a source. 57 | // It does not implement sound.Source, since the sample rate is 58 | // fixed. 59 | type DynResampler struct { 60 | ct *C 61 | src sound.Source 62 | conv SampleRateConverter 63 | lasti float64 64 | buf []float64 65 | } 66 | 67 | // NewDynResampler creates a new Dynamic Resampler from a continuous 68 | // time representation and a sample rate converter. 69 | func NewDynResampler(c *C, conv SampleRateConverter) *DynResampler { 70 | return &DynResampler{ct: c, conv: conv, lasti: 0.0, buf: make([]float64, c.Channels())} 71 | } 72 | 73 | // DynResampler returns the number of channels. 74 | func (r *DynResampler) Channels() int { 75 | return r.ct.src.Channels() 76 | } 77 | 78 | // Close implements sound.Close 79 | func (r *DynResampler) Close() error { 80 | return r.ct.Close() 81 | } 82 | 83 | // Receive is as in sound.Source.Receive. 84 | func (r *DynResampler) Receive(d []float64) (int, error) { 85 | nC := r.ct.Channels() 86 | if len(d)%nC != 0 { 87 | return 0, sound.ErrChannelAlignment 88 | } 89 | nF := len(d) / nC 90 | for f := 0; f < nF; f++ { 91 | if err := r.ct.FrameAt(r.buf, r.lasti); err != nil { 92 | if err == io.EOF { 93 | cil.Compact(d, nC, f) 94 | return f, nil 95 | } 96 | return 0, err 97 | } 98 | r.lasti += r.conv.Convert() 99 | for c := range r.buf { 100 | d[c*nF+f] = r.buf[c] 101 | } 102 | } 103 | return nF, nil 104 | } 105 | 106 | type constResampler struct { 107 | *DynResampler 108 | outRate freq.T 109 | } 110 | 111 | // SampleRate returns the output sample rate of c. 112 | func (c *constResampler) SampleRate() freq.T { 113 | return c.outRate 114 | } 115 | 116 | // Resample takes a sound.Source src, a desired samplerate r, and 117 | // an interpolator itp. 118 | // 119 | // If itp is nil, it will default to a high quality interpolator 120 | // (order 10 Blackman windowed sinc interpolation). 121 | // 122 | // Resample returns a sound.Source whose SampleRate() is equal to 123 | // r. 124 | // 125 | // After a call to Resample, either the Receive method of src 126 | // should not be called, or the Receive method of the result 127 | // should not be called. Clearly, the former is the usual use case. 128 | func Resample(src sound.Source, r freq.T, itp Itper) sound.Source { 129 | sr := src.SampleRate() 130 | if sr == r { 131 | return src 132 | } 133 | tr := float64(sr) / float64(r) 134 | conv := constSampleRateConverter(tr) 135 | ct := NewC(src, itp) 136 | dyn := NewDynResampler(ct, conv) 137 | return &constResampler{DynResampler: dyn, outRate: r} 138 | } 139 | 140 | // NewC creates a new continuous time representation 141 | // of the source src using an interpolator itp. 142 | // 143 | // if itp is nil, then a default interpolator of high 144 | // quality will be used (order 10 Blackman windowed sinc interpolation). 145 | // 146 | // NewC calls src.Receive in this process, so src.Receive 147 | // should not be called if the resulting continuous time 148 | // interface is used. 149 | func NewC(src sound.Source, itp Itper) *C { 150 | order := 10 151 | if itp != nil { 152 | order = itp.Order() 153 | } 154 | if itp == nil { 155 | n := 2 * order 156 | m := float64(n - 1) 157 | r := 2 * math.Pi / m 158 | itp = NewWinSinc(order, wfn.Stretch(wfn.Blackman, r)) 159 | } 160 | nC := src.Channels() 161 | sz := 2*order + shiftSize 162 | cbufs := make([][]float64, nC) 163 | for i := range cbufs { 164 | cbufs[i] = make([]float64, sz) 165 | } 166 | rbuf := make([]float64, shiftSize*nC) 167 | return &C{ 168 | src: src, 169 | shift: shiftSize, 170 | bufSize: sz, 171 | off: -sz, 172 | itper: itp, 173 | eps: 0.0000000001, 174 | cbufs: cbufs, 175 | rbuf: rbuf} 176 | } 177 | 178 | var errMultiChanAt = errors.New("ErrMultiChanAt") 179 | 180 | // At returns a continuous time interpolated sample at index i. 181 | // It is the equivalent of 182 | // 183 | // var buf [1]float64 184 | // if err := c.FrameAt(buf[:], i); err != nil { 185 | // return 0.0, err 186 | // } 187 | // return buf[0], nil 188 | // 189 | func (c *C) At(i float64) (float64, error) { 190 | if c.Channels() != 1 { 191 | return 0.0, errMultiChanAt 192 | } 193 | var buf [1]float64 194 | if err := c.FrameAt(buf[:], i); err != nil { 195 | return 0.0, err 196 | } 197 | return buf[0], nil 198 | } 199 | 200 | // Channels returns the number of channels of the source to 201 | // which c provides continuous time access. 202 | func (c *C) Channels() int { 203 | return c.src.Channels() 204 | } 205 | 206 | // Close closes the underlying source and returns the resulting error. 207 | func (c *C) Close() error { 208 | return c.src.Close() 209 | } 210 | 211 | // FrameAt places a continuous time interpolated frame at index i in 212 | // dst. 213 | // 214 | // FrameAt should be called with i increasing monotonically to guarantee that c 215 | // does not need to go back in time arbitrarily in its underlying source. 216 | // 217 | // If i is not increasing monotonically, the behavior of FrameAt is undefined. 218 | // 219 | // FrameAt returns a non-nil error if i >= the number of samples available in 220 | // the underlying source without returning an error. The returned error is 221 | // that returned from the underlying source. 222 | // 223 | // At the edges, where insufficient or no neighbors are available, the 224 | // interpolation is truncated symmetrically. 225 | // 226 | // FrameAt returns sound.ErrChannelAlignment if len(dst) != c.Channels(). 227 | func (c *C) FrameAt(dst []float64, i float64) error { 228 | nC := c.Channels() 229 | if len(dst) != nC { 230 | return sound.ErrChannelAlignment 231 | } 232 | jf, jr := math.Modf(i) 233 | j := int(jf) 234 | order := c.itper.Order() 235 | for j+order >= c.off+c.bufSize { 236 | if c.err != nil { 237 | return c.err 238 | } 239 | n, e := c.src.Receive(c.rbuf) 240 | if e != nil { 241 | c.err = e 242 | } 243 | c.off += n 244 | for i, cb := range c.cbufs { 245 | copy(cb, cb[c.shift:]) 246 | copy(cb[len(cb)-c.shift:], c.rbuf[i*n:(i+1)*n]) 247 | } 248 | } 249 | itp := c.itper 250 | for ci := range dst { 251 | buf := c.cbufs[ci] 252 | cj := j - c.off 253 | if jr <= c.eps || (1-jr) <= c.eps { 254 | dst[ci] = buf[cj] 255 | continue 256 | } 257 | if cj+order >= len(buf) { 258 | order = len(buf) - 1 - cj 259 | } 260 | if cj-order < 0 { 261 | order = cj 262 | } 263 | if order == 0 { 264 | dst[ci] = buf[cj] 265 | continue 266 | } 267 | r := itp.Itp(buf[cj-order+1:cj+order+1], float64(order-1)+jr) 268 | dst[ci] = r 269 | } 270 | return nil 271 | } 272 | -------------------------------------------------------------------------------- /resample/ct_test.go: -------------------------------------------------------------------------------- 1 | package resample 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/zikichombo/sound/freq" 8 | "github.com/zikichombo/sound/gen" 9 | "github.com/zikichombo/sound/ops" 10 | ) 11 | 12 | func TestCDefaultMonoChan(t *testing.T) { 13 | gnr := gen.New(44100 * freq.Hertz) 14 | rps := (44100 * freq.Hertz).RadsPer(800 * freq.Hertz) 15 | rps /= 10 16 | d := 0.0 17 | src := gnr.Sin(800 * freq.Hertz) 18 | c := NewC(src, nil) 19 | d = 0.0 20 | err := 0.0 21 | for i := 0; i < 10000; i++ { 22 | fi := float64(i) / 10.0 23 | v, e := c.At(fi) 24 | if e != nil { 25 | t.Fatal(e) 26 | } 27 | //fmt.Printf("%d: order %d itp %f org %f err %f\n", i, o, v, math.Sin(d), math.Abs(v-math.Sin(d))) 28 | err += math.Abs(v - math.Sin(d)) 29 | d += rps 30 | } 31 | eps := err / 10000 32 | if eps > 0.1 { 33 | t.Errorf("default error per sample too large: %f\n", eps) 34 | } 35 | //fmt.Printf("default error per sample %f:\n", err/10000) 36 | } 37 | 38 | func TestCDefaultMultiChan(t *testing.T) { 39 | gnr := gen.New(44100 * freq.Hertz) 40 | rps := (44100 * freq.Hertz).RadsPer(800 * freq.Hertz) 41 | rps /= 10 42 | d := 0.0 43 | src0, src1 := gnr.Sin(800*freq.Hertz), gnr.Sin(800*freq.Hertz) 44 | c := NewC(ops.MustJoin(src0, src1), nil) 45 | d = 0.0 46 | err := 0.0 47 | frame := make([]float64, 2) 48 | for i := 0; i < 10000; i++ { 49 | fi := float64(i) / 10.0 50 | if e := c.FrameAt(frame, fi); e != nil { 51 | t.Fatal(e) 52 | } 53 | //fmt.Printf("%d: order %d itp %f org %f err %f\n", i, o, v, math.Sin(d), math.Abs(v-math.Sin(d))) 54 | for _, v := range frame { 55 | err += math.Abs(v - math.Sin(d)) 56 | } 57 | d += rps 58 | } 59 | eps := err / 20000 60 | if eps > 0.1 { 61 | t.Errorf("default error per sample too large: %f\n", eps) 62 | } 63 | } 64 | 65 | func TestCSinc(t *testing.T) { 66 | gnr := gen.New(44100 * freq.Hertz) 67 | rps := (44100 * freq.Hertz).RadsPer(800 * freq.Hertz) 68 | rps /= 10 69 | d := 0.0 70 | for o := 1; o <= 30; o++ { 71 | src := gnr.Sin(800 * freq.Hertz) 72 | itper := NewSincItp(o) 73 | c := NewC(src, itper) 74 | d = 0.0 75 | err := 0.0 76 | for i := 0; i < 10000; i++ { 77 | fi := float64(i) / 10.0 78 | v, e := c.At(fi) 79 | if e != nil { 80 | t.Fatal(e) 81 | } 82 | //fmt.Printf("%d: order %d itp %f org %f err %f\n", i, o, v, math.Sin(d), math.Abs(v-math.Sin(d))) 83 | err += math.Abs(v - math.Sin(d)) 84 | d += rps 85 | } 86 | eps := err / 10000 87 | if eps > 0.5/float64(o) { 88 | t.Errorf("sinc %d error per sample too large: %f\n", o, eps) 89 | } 90 | //fmt.Printf("sinc %d error per sample %f:\n", o, err/10000) 91 | } 92 | } 93 | 94 | func TestCLanczos(t *testing.T) { 95 | gnr := gen.New(44100 * freq.Hertz) 96 | rps := (44100 * freq.Hertz).RadsPer(800 * freq.Hertz) 97 | rps /= 10 98 | d := 0.0 99 | for a := 1; a < 5; a++ { 100 | for o := 1; o <= 20; o++ { 101 | src := gnr.Sin(800 * freq.Hertz) 102 | itper := NewLanczos(o, a) 103 | c := NewC(src, itper) 104 | d = 0.0 105 | err := 0.0 106 | for i := 0; i < 10000; i++ { 107 | fi := float64(i) / 10.0 108 | v, e := c.At(fi) 109 | if e != nil { 110 | t.Fatal(e) 111 | } 112 | //fmt.Printf("%d: order %d itp %f org %f err %f\n", i, o, v, math.Sin(d), math.Abs(v-math.Sin(d))) 113 | err += math.Abs(v - math.Sin(d)) 114 | d += rps 115 | } 116 | eps := err / 10000 117 | if eps > 0.2/float64(o) && a != 1 { 118 | t.Errorf("lanczos order %d stretch %d error per sample too large: %f\n", o, a, eps) 119 | } 120 | //fmt.Printf("lanczos order %d stretch %d error per sample %f:\n", o, a, err/10000) 121 | } 122 | } 123 | } 124 | 125 | func TestResampleConst(t *testing.T) { 126 | fa := 800 * freq.Hertz 127 | src := gen.Sin(fa) 128 | r := 48000 * freq.Hertz 129 | rez := Resample(src, r, nil) 130 | rps := fa.RadsPerAt(rez.SampleRate()) 131 | rads := 0.0 132 | N := 1000 133 | C := 4 134 | d := make([]float64, N) 135 | err := 0.0 136 | for c := 0; c < C; c++ { 137 | n, e := rez.Receive(d) 138 | if e != nil { 139 | t.Fatal(e) 140 | } 141 | if n != N { 142 | t.Fatalf("got %d frames not %d\n", n, N) 143 | } 144 | for i := range d { 145 | ref := math.Sin(rads) 146 | rads += rps 147 | got := d[i] 148 | err += math.Abs(got - ref) 149 | } 150 | } 151 | if err > 0.001*float64(N)*float64(C) { 152 | t.Errorf("resample error too large %f\n", err) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /resample/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The ZikiChombo Authors. All rights reserved. Use of 2 | // this source code is governed by a license that can be found in the License 3 | // file. 4 | 5 | // Package resample implements resampling/changes in resolution. 6 | // 7 | // Package resample uses interpolation for resampling which provides easy 8 | // control over the quality/cost tradeoff and can produce very high quality 9 | // resampling. Other resampling methods may be more appropriate for a given 10 | // calling context, package resample doesn't yet provide other mechanisms. 11 | // 12 | // When resampling audio, any decrease in sample rate from rate S to a rate R 13 | // must be applied to a signal which does not contain frequencies at or above 14 | // R/2, or aliasing will produce strange results. 15 | // 16 | // This is often achieved by first applying a low pass filter and then 17 | // resampling. As ZikiChombo does not yet have filter design support, we 18 | // recommend in the meantime simply taking a moving average of the signal with 19 | // a window size W = ceil(S/R) before decreasing the sample rate if you do not 20 | // have access to or knowledge about low pass filtering design. 21 | // 22 | // 23 | // BUG(wsc) the shift size, effecting interpolation order limits and 24 | // latency of implementations is constant (64 frames). 25 | package resample 26 | -------------------------------------------------------------------------------- /resample/itp.go: -------------------------------------------------------------------------------- 1 | package resample 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/zikichombo/dsp/wfn" 7 | ) 8 | 9 | // Itper provides an interface for an interpolator. 10 | type Itper interface { 11 | // Order returns the maximum number of discrete neighbors on either 12 | // side of the point to be interpolated. 13 | Order() int 14 | 15 | // Itp performs the interpolation for point i. i must be in 16 | // the range [0..2*Itper.Order()). Interpolation neighborhood 17 | // is truncated according to the bounds of neighbors. 18 | Itp(neighbors []float64, i float64) float64 19 | 20 | // ItpCirc interpolates for point i in neighbors supposing 21 | // neighbors is circularly wrapped. This is useful for 22 | // when neighbors is in a circular buffer and the context 23 | // guarantees there are sufficient neighbors around index i. 24 | CircItp(neighbors []float64, i float64) float64 25 | } 26 | 27 | type linItp struct{} 28 | 29 | func (l *linItp) Order() int { 30 | return 1 31 | } 32 | 33 | func (l *linItp) Itp(nbrs []float64, p float64) float64 { 34 | pi, pf := math.Modf(p) 35 | q := int(pi) 36 | return (1-pf)*nbrs[q] + pf*nbrs[q+1] 37 | } 38 | 39 | func (l *linItp) CircItp(nbrs []float64, p float64) float64 { 40 | pi, pf := math.Modf(p) 41 | q := int(pi) % len(nbrs) 42 | r := q + 1 43 | if r == len(nbrs) { 44 | r = 0 45 | } 46 | return (1-pf)*nbrs[q] + pf*nbrs[r] 47 | } 48 | 49 | // LinItp returns a linear interpolator. 50 | func LinItp() Itper { 51 | return &linItp{} 52 | } 53 | 54 | type fItp struct { 55 | order int 56 | fn func(float64) float64 57 | } 58 | 59 | // NewFnItp returns a new interpolator from a weighting function fn. 60 | // 61 | // The function fn should accept values in the range (-o..o) 62 | // giving the (signed) distance to the point to be interpolated. 63 | // It should return an appropriate weight for input point at the specified 64 | // distance. 65 | func NewFnItp(o int, fn func(dist float64) float64) Itper { 66 | return &fItp{order: o, fn: fn} 67 | } 68 | 69 | func (i *fItp) Order() int { 70 | return i.order 71 | } 72 | 73 | func (i *fItp) Itp(nbrs []float64, p float64) float64 { 74 | acc := 0.0 75 | order := i.order 76 | qf, qr := math.Modf(p) 77 | q := int(qf) 78 | fn := i.fn 79 | for o := 0; o < order; o++ { 80 | l, r := q-o, q+o+1 81 | if l < 0 || r >= len(nbrs) { 82 | break 83 | } 84 | fo := float64(o) 85 | acc += fn(-(fo + qr)) * nbrs[l] 86 | acc += fn(fo+(1-qr)) * nbrs[r] 87 | } 88 | return acc 89 | } 90 | 91 | func (i *fItp) CircItp(nbrs []float64, p float64) float64 { 92 | acc := 0.0 93 | order := i.order 94 | qf, qr := math.Modf(p) 95 | q := int(qf) % len(nbrs) 96 | fn := i.fn 97 | for o := 0; o < order; o++ { 98 | l, r := q-o, q+o+1 99 | if l < 0 { 100 | l += len(nbrs) 101 | } 102 | if r >= len(nbrs) { 103 | r -= len(nbrs) 104 | } 105 | fo := float64(o) 106 | acc += fn(-(fo + qr)) * nbrs[l] 107 | acc += fn(fo+(1-qr)) * nbrs[r] 108 | } 109 | return acc 110 | } 111 | 112 | // NewSincItp returns a new Sinc interpolator from the Shannon 113 | // interpolation theorem. 114 | func NewSincItp(o int) Itper { 115 | return &fItp{order: o, fn: wfn.Sinc} 116 | } 117 | 118 | // NewWinSinc returns a new windowed sinc interpolator where 119 | // the interpolation weighting function is a windowed sinc 120 | // windowed by wf. 121 | func NewWinSinc(o int, wf func(float64) float64) Itper { 122 | ws := func(d float64) float64 { 123 | return wfn.Sinc(d) * wf(d) 124 | } 125 | return &fItp{order: o, fn: ws} 126 | } 127 | 128 | // NewLanczos returns a new Lanczos interpolator with 129 | // stretch "stretch" of order order. 130 | func NewLanczos(order, stretch int) Itper { 131 | sf := func(d float64) float64 { 132 | return wfn.LanczosItp(stretch, d) 133 | } 134 | return &fItp{order: order, fn: sf} 135 | } 136 | -------------------------------------------------------------------------------- /resample/itp_test.go: -------------------------------------------------------------------------------- 1 | package resample 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/zikichombo/dsp/wfn" 9 | ) 10 | 11 | var d = []float64{0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 12 | -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1} 13 | 14 | func TestItpLin(t *testing.T) { 15 | itper := LinItp() 16 | n := float64(len(d) - 1) 17 | p := rand.Float64() * n 18 | v := itper.Itp(d, p) 19 | pi, _ := math.Modf(p) 20 | l := int(pi) 21 | r := l + 1 22 | if !between(d[l], d[r], v) { 23 | t.Errorf("bad linear interpolation: %f not in {%f..%f}", v, d[l], d[r]) 24 | } 25 | } 26 | 27 | func TestItpSinc(t *testing.T) { 28 | N := len(d) / 2 29 | N-- 30 | ct := 0 31 | for o := 1; o < N; o++ { 32 | itper := NewSincItp(o) 33 | p := rand.Float64() * float64(N) 34 | v := itper.Itp(d, p) 35 | pi, _ := math.Modf(p) 36 | l := int(pi) 37 | r := l + 1 38 | if !between(d[l], d[r], v) { 39 | ct++ 40 | } 41 | //fmt.Printf("sinc order %d {%f %f} of %f itp %f\n", o, d[l], d[r], pr, v) 42 | } 43 | if ct > 5 { 44 | t.Errorf("too many out of whack sinc interpolations: %d/%d\n", ct, N) 45 | } 46 | } 47 | 48 | func TestItpWinSinc(t *testing.T) { 49 | N := len(d) / 2 50 | N-- 51 | ct := 0 52 | for o := 1; o < N; o++ { 53 | itper := NewWinSinc(o, wfn.Stretch(wfn.Blackman, math.Pi/float64(o))) 54 | p := rand.Float64() * float64(N) 55 | v := itper.Itp(d, p) 56 | pi, _ := math.Modf(p) 57 | l := int(pi) 58 | r := l + 1 59 | if !between(d[l], d[r], v) { 60 | ct++ 61 | } 62 | //fmt.Printf("win sinc order %d {%f %f} of %f itp %f\n", o, d[l], d[r], pr, v) 63 | } 64 | if ct > 5 { 65 | t.Errorf("windowed sinc interpolation too many not between: %d/%d\n", ct, N) 66 | } 67 | } 68 | 69 | func TestItpLanczos(t *testing.T) { 70 | N := len(d) / 2 71 | N-- 72 | ct := 0 73 | for a := 1; a < 5; a++ { 74 | for o := 1; o < N; o++ { 75 | itper := NewLanczos(o, a) 76 | p := rand.Float64() * float64(N) 77 | v := itper.Itp(d, p) 78 | pi, _ := math.Modf(p) 79 | l := int(pi) 80 | r := l + 1 81 | if !between(d[l], d[r], v) { 82 | ct++ 83 | } 84 | //fmt.Printf("lanczos order %d stretch %d {%f %f} of %f itp %f\n", o, a, d[l], d[r], pr, v) 85 | } 86 | } 87 | if ct > 5 { 88 | t.Errorf("too many outliers: %d/%d\n", ct, N) 89 | } 90 | } 91 | 92 | func between(l, r, v float64) bool { 93 | if l > r { 94 | l, r = r, l 95 | } 96 | l -= 0.01 97 | r += 0.01 98 | return v >= l && v <= r 99 | } 100 | -------------------------------------------------------------------------------- /wfn/blackman.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package wfn 5 | 6 | import "math" 7 | 8 | func Blackman(i float64) float64 { 9 | return 0.42 - 0.5*math.Cos(math.Pi-i) + 0.08*math.Cos(2*(math.Pi-i)) 10 | } 11 | -------------------------------------------------------------------------------- /wfn/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | // Package wfn provides support for time windowing functions. 5 | // 6 | package wfn 7 | -------------------------------------------------------------------------------- /wfn/hamming.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package wfn 5 | 6 | import "math" 7 | 8 | func Hamming(i float64) float64 { 9 | return 0.53836 - 0.46164*math.Cos(math.Pi-i) 10 | } 11 | -------------------------------------------------------------------------------- /wfn/hann.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package wfn 5 | 6 | import "math" 7 | 8 | func Hann(i float64) float64 { 9 | return 0.5 - 0.5*math.Cos(math.Pi-i) 10 | } 11 | -------------------------------------------------------------------------------- /wfn/lanczos.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package wfn 5 | 6 | func LanczosItp(a int, t float64) float64 { 7 | return Sinc(t) * Sinc(t/float64(a)) 8 | } 9 | 10 | func LanczosItpFn(a int) func(float64) float64 { 11 | return func(t float64) float64 { 12 | return LanczosItp(a, t) 13 | } 14 | } 15 | 16 | func LanczosWin(a int, t float64) float64 { 17 | return Sinc(t / float64(a)) 18 | } 19 | -------------------------------------------------------------------------------- /wfn/sinc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package wfn 5 | 6 | import "math" 7 | 8 | func Sinc(x float64) float64 { 9 | return SincEps(x, 1e-10) 10 | } 11 | 12 | func SincEps(x, eps float64) float64 { 13 | if math.Abs(x) < eps { 14 | return 1 15 | } 16 | return math.Sin(math.Pi*x) / (math.Pi * x) 17 | } 18 | -------------------------------------------------------------------------------- /wfn/stretch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package wfn 5 | 6 | func Stretch(fn func(float64) float64, by float64) func(float64) float64 { 7 | return func(i float64) float64 { 8 | return fn(by * i) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /wfn/t.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The ZikiChombo Authors. All rights reserved. Use of this source 2 | // code is governed by a license that can be found in the License file. 3 | 4 | package wfn 5 | 6 | import "math" 7 | 8 | // T describes a concrete window function. 9 | type T []float64 10 | 11 | // Given a function f with domain from [-pi..pi), return a window 12 | // dividing [-pi..pi) into n values. 13 | func New(f func(float64) float64, n int) T { 14 | m := float64(n - 1) 15 | r := 2 * math.Pi / m 16 | h := m / 2 17 | res := make([]float64, n) 18 | for i := 0; i < n; i++ { 19 | fi := float64(i) 20 | res[i] = f(r * (fi - h)) 21 | } 22 | return T(res) 23 | } 24 | 25 | // Apply applies the window t to the data t. 26 | // Apply panics if len(d) > len(t). 27 | func (t T) Apply(d []float64) { 28 | for i := range d { 29 | d[i] *= t[i] 30 | } 31 | } 32 | 33 | // DcGain returns the power of constant value 1 signal in 34 | func (t T) DcGain() float64 { 35 | ttl := 0.0 36 | for _, v := range t { 37 | ttl += math.Abs(v) 38 | } 39 | return ttl / float64(len(t)) 40 | } 41 | 42 | // AvGain computes gain which is the ratio of sum of coeficients in vs 43 | // to its length. Since vs can contain negative and zero coeficients, 44 | // AvGain can give a zero result and normalizing accordingly is risky. 45 | func (t T) AvGain() float64 { 46 | ttl := 0.0 47 | for _, v := range t { 48 | ttl += v 49 | } 50 | return ttl / float64(len(t)) 51 | } 52 | 53 | // DcNorm normalizes vs according to unity DcGain 54 | func (t T) DcNorm() { 55 | g := t.DcGain() * float64(len(t)) 56 | for i := range t { 57 | t[i] /= g 58 | } 59 | } 60 | 61 | // AvNorm normalizes vs according to unity AvGain. 62 | func (t T) AvNorm() { 63 | g := t.AvGain() * float64(len(t)) 64 | for i := range t { 65 | t[i] /= g 66 | } 67 | } 68 | --------------------------------------------------------------------------------