roctave 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +33 -0
  4. data/ext/roctave/cu8_file_reader.c +331 -0
  5. data/ext/roctave/cu8_file_reader.h +30 -0
  6. data/ext/roctave/extconf.rb +6 -0
  7. data/ext/roctave/fir_filter.c +795 -0
  8. data/ext/roctave/fir_filter.h +29 -0
  9. data/ext/roctave/freq_shifter.c +410 -0
  10. data/ext/roctave/freq_shifter.h +29 -0
  11. data/ext/roctave/iir_filter.c +462 -0
  12. data/ext/roctave/iir_filter.h +29 -0
  13. data/ext/roctave/roctave.c +38 -0
  14. data/ext/roctave/roctave.h +27 -0
  15. data/lib/roctave.rb +168 -0
  16. data/lib/roctave/bilinear.rb +92 -0
  17. data/lib/roctave/butter.rb +87 -0
  18. data/lib/roctave/cheby.rb +180 -0
  19. data/lib/roctave/cu8_file_reader.rb +45 -0
  20. data/lib/roctave/dft.rb +280 -0
  21. data/lib/roctave/filter.rb +64 -0
  22. data/lib/roctave/finite_difference_coefficients.rb +73 -0
  23. data/lib/roctave/fir.rb +121 -0
  24. data/lib/roctave/fir1.rb +134 -0
  25. data/lib/roctave/fir2.rb +246 -0
  26. data/lib/roctave/fir_design.rb +311 -0
  27. data/lib/roctave/firls.rb +380 -0
  28. data/lib/roctave/firpm.rb +499 -0
  29. data/lib/roctave/freq_shifter.rb +47 -0
  30. data/lib/roctave/freqz.rb +233 -0
  31. data/lib/roctave/iir.rb +80 -0
  32. data/lib/roctave/interp1.rb +78 -0
  33. data/lib/roctave/plot.rb +748 -0
  34. data/lib/roctave/poly.rb +46 -0
  35. data/lib/roctave/roots.rb +73 -0
  36. data/lib/roctave/sftrans.rb +157 -0
  37. data/lib/roctave/version.rb +3 -0
  38. data/lib/roctave/window.rb +116 -0
  39. data/roctave.gemspec +79 -0
  40. data/samples/butter.rb +12 -0
  41. data/samples/cheby.rb +28 -0
  42. data/samples/dft.rb +18 -0
  43. data/samples/differentiator.rb +48 -0
  44. data/samples/differentiator_frequency_scaling.rb +52 -0
  45. data/samples/fft.rb +40 -0
  46. data/samples/finite_difference_coefficient.rb +53 -0
  47. data/samples/fir1.rb +13 -0
  48. data/samples/fir2.rb +14 -0
  49. data/samples/fir2_windows.rb +29 -0
  50. data/samples/fir2bank.rb +30 -0
  51. data/samples/fir_low_pass.rb +44 -0
  52. data/samples/firls.rb +77 -0
  53. data/samples/firpm.rb +78 -0
  54. data/samples/hilbert_transformer.rb +20 -0
  55. data/samples/hilbert_transformer_frequency_scaling.rb +47 -0
  56. data/samples/plot.rb +45 -0
  57. data/samples/stem.rb +8 -0
  58. data/samples/type1.rb +25 -0
  59. data/samples/type3.rb +24 -0
  60. data/samples/windows.rb +25 -0
  61. metadata +123 -0
@@ -0,0 +1,45 @@
1
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
2
+ ##
3
+ ## This file is part of Roctave
4
+ ##
5
+ ## Roctave is free software: you can redistribute it and/or modify
6
+ ## it under the terms of the GNU General Public License as published by
7
+ ## the Free Software Foundation, either version 3 of the License, or
8
+ ## (at your option) any later version.
9
+ ##
10
+ ## Roctave is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+ ##
15
+ ## You should have received a copy of the GNU General Public License
16
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ module Roctave
20
+ class CU8FileReader
21
+ # Read the content of the file as couples of real and imaginary unsigned chars,
22
+ # and return an array of complex numbers whose real and imaginary parts are floating
23
+ # point numbers in the range [-1.0, 1.0]
24
+ # @param filename [String] the path of the file to read
25
+ # @return [Array<Float>] array of complex numbers read from the file
26
+ def self.read (filename)
27
+ reader = Roctave::CU8FileReader.new(filename)
28
+ arr = reader.read
29
+ reader.close
30
+ return arr
31
+ end
32
+
33
+
34
+ # Creates a new CU8FileReader with the given file name, yields it,
35
+ # then close it and return the result of the block.
36
+ # @param filename [String] the path of the file to read
37
+ def self.open (filename)
38
+ reader = Roctave::CU8FileReader.new(filename)
39
+ res = yield(reader)
40
+ reader.close
41
+ return res
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,280 @@
1
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
2
+ ##
3
+ ## This file is part of Roctave
4
+ ##
5
+ ## Roctave is free software: you can redistribute it and/or modify
6
+ ## it under the terms of the GNU General Public License as published by
7
+ ## the Free Software Foundation, either version 3 of the License, or
8
+ ## (at your option) any later version.
9
+ ##
10
+ ## Roctave is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+ ##
15
+ ## You should have received a copy of the GNU General Public License
16
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ module Roctave
20
+ # @!group Fourier transform
21
+
22
+ ##
23
+ # Compute the discrete Fourier transform of +x+.
24
+ # If called with two arguments, +n+ is expected to be an integer
25
+ # specifying the number of elements of +x+ to use.
26
+ # If +n+ is larger than the length of +x+, then +x+ is resized
27
+ # and padded with zeros. Otherwise, if +n+ is smaller than the
28
+ # length of +x+, then +x+ is truncated.
29
+ # @param x [Array<Float>, Array<Complex>] The input signal.
30
+ # @param n [Integer, nil] The length of the DFT, defaults to the lengs of +x+.
31
+ # @param window [Array<Float>, Symbol] Premultiply the input signal by the window, eigher an array of length +n+, or a window symbol (ex: :blackman).
32
+ # @param normalize [Boolean] Divide the output by the area under the window.
33
+ # @return [Array<Complex>] The result of the DFT.
34
+ def self.dft (x, n = nil, window: nil, normalize: false)
35
+ xlen = x.length
36
+ if n then
37
+ n = [1, n.to_i].max
38
+ len = n
39
+ xlen = [xlen, n].min
40
+ else
41
+ len = x.length
42
+ end
43
+ return Roctave.fft(x, n, window: window, normalize: normalize) if Math.log2(len).floor == Math.log2(len)
44
+
45
+ case window
46
+ when Array
47
+ raise ArgumentError.new "The window must have the same size as the input array" if window.length < xlen
48
+ x = (0...xlen).collect{|i| x[i] * window[i]}
49
+ when Symbol
50
+ window = Roctave.send(window, xlen)
51
+ x = (0...xlen).collect{|i| x[i] * window[i]}
52
+ when nil
53
+ else
54
+ raise ArgumentError.new "The window must an array or a symbol"
55
+ end
56
+
57
+ if normalize then
58
+ if window then
59
+ factor = window.inject(&:+).to_f
60
+ else
61
+ factor = xlen.to_f
62
+ end
63
+ else
64
+ factor = 1.0
65
+ end
66
+
67
+ y = (0...len).collect do |k|
68
+ acc = Complex(0)
69
+ (0...xlen).each do |i|
70
+ acc += Complex.polar(1.0, -2.0*Math::PI*k*i / len) * x[i]
71
+ end
72
+ acc / factor
73
+ end
74
+ y
75
+ end
76
+
77
+
78
+ ##
79
+ # Compute the inverse discrete Fourier transform of +x+.
80
+ # If called with two arguments, +n+ is expected to be an integer
81
+ # specifying the number of elements of +x+ to use.
82
+ # If +n+ is larger than the length of +x+, then +x+ is resized
83
+ # and padded with zeros. Otherwise, if +n+ is smaller than the
84
+ # length of +x+, then +x+ is truncated.
85
+ # @param x [Array<Float>, Array<Complex>] The input signal.
86
+ # @param n [Integer, nil] The length of the DFT, defaults to the lengs of +x+.
87
+ # @return [Array<Complex>] The result of the DFT.
88
+ def self.idft (x, n = nil)
89
+ xlen = x.length
90
+ if n then
91
+ n = [1, n.to_i].max
92
+ len = n
93
+ xlen = [xlen, n].min
94
+ else
95
+ len = x.length
96
+ end
97
+ return Roctave.ifft(x, n) if Math.log2(len).floor == Math.log2(len)
98
+
99
+
100
+ y = (0...len).collect do |k|
101
+ acc = Complex(0)
102
+ (0...xlen).each do |i|
103
+ acc += Complex.polar(1.0, 2.0*Math::PI*k*i / len) * x[i]
104
+ end
105
+ acc / len
106
+ end
107
+ y
108
+ end
109
+
110
+
111
+ ##
112
+ # same as +dft+, but computes ~30x faster.
113
+ # The length of the FFT is rounded to the neareast power of two.
114
+ # @see dft
115
+ def self.fft(x, n = nil, window: nil, normalize: false)
116
+ if n then
117
+ len = [1, n.to_i].max
118
+ else
119
+ len = x.length
120
+ end
121
+ prevpow2 = 2**(Math.log2(len).floor)
122
+ nextpow2 = 2**(Math.log2(len).ceil)
123
+ if (nextpow2 - len) <= (len - prevpow2) then
124
+ len = nextpow2
125
+ else
126
+ len = prevpow2
127
+ end
128
+ wlen = [x.length, len].min
129
+
130
+ case window
131
+ when Array
132
+ raise ArgumentError.new "The window must be of size #{wlen}" unless window.length == wlen
133
+ x = (0...wlen).collect{|i| x[i] * window[i]}
134
+ when Symbol
135
+ window = Roctave.send(window, wlen)
136
+ x = (0...wlen).collect{|i| x[i] * window[i]}
137
+ when nil
138
+ else
139
+ raise ArgumentError.new "The window must be an array or a symbol"
140
+ end
141
+
142
+ if x.length != len then
143
+ x = (0...len).collect{|i| x[i].nil? ? 0.0 : x[i]}
144
+ end
145
+
146
+ y = iterative_fft(x)
147
+
148
+ if normalize then
149
+ if window then
150
+ factor = window.inject(&:+).to_f
151
+ else
152
+ factor = wlen.to_f
153
+ end
154
+ y.collect!{|v| v / factor}
155
+ end
156
+
157
+ y
158
+ end
159
+
160
+
161
+ ##
162
+ # Used internaly. Use Roctave.fft instead of calling directly this function.
163
+ # Implements the Cooley-Tukey FFT
164
+ def self.iterative_fft (arr_in)
165
+ ## Implementation from https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm 2019-06-11
166
+ n = arr_in.length
167
+ nbl = Math.log2(n)
168
+ raise ArgumentError.new "Input array must have a length power of two" if nbl.floor != nbl
169
+ nbl = nbl.to_i
170
+
171
+ ## bit-reverse copy
172
+ arr = (0...n).collect do |i|
173
+ arr_in[i.to_s(2).rjust(nbl, '0').reverse.to_i(2)]
174
+ end
175
+
176
+ (1..nbl).each do |s|
177
+ m = 2**s
178
+ wm = Complex.polar(1.0, -2.0*Math::PI/m)
179
+ (0...n).step(m).each do |k|
180
+ w = Complex(1.0)
181
+ (0 ... m/2).each do |j|
182
+ t = w*arr[k+j+m/2]
183
+ u = arr[k+j]
184
+ arr[k+j] = u + t
185
+ arr[k+j+m/2] = u - t
186
+ w = w * wm
187
+ end
188
+ end
189
+ end
190
+
191
+ arr
192
+ end
193
+
194
+
195
+ # @param x [Array]
196
+ # @return [Array]
197
+ def self.fftshift (x)
198
+ xl = x.length
199
+ xx = (xl / 2.0).ceil
200
+ y = Array(xl)
201
+ i = 0
202
+ (xx...xl).each do |j|
203
+ y[i] = x[j]
204
+ i += 1
205
+ end
206
+ (0...xx).each do |j|
207
+ y[i] = x[j]
208
+ i += 1
209
+ end
210
+ y
211
+ end
212
+
213
+
214
+ ##
215
+ # same as +idft+, but computes ~30x faster.
216
+ # The length of the iFFT is rounded to the neareast power of two.
217
+ # @see dft
218
+ def self.ifft(x, n = nil, normalized_input: false)
219
+ if n then
220
+ len = [1, n.to_i].max
221
+ else
222
+ len = x.length
223
+ end
224
+ prevpow2 = 2**(Math.log2(len).floor)
225
+ nextpow2 = 2**(Math.log2(len).ceil)
226
+ if (nextpow2 - len) <= (len - prevpow2) then
227
+ len = nextpow2
228
+ else
229
+ len = prevpow2
230
+ end
231
+ wlen = [x.length, len].min
232
+
233
+ if x.length != len then
234
+ x = (0...len).collect{|i| x[i].nil? ? 0.0 : x[i]}
235
+ end
236
+
237
+ y = iterative_ifft(x)
238
+
239
+ unless normalized_input then
240
+ y.collect!{|v| v / len}
241
+ end
242
+
243
+ y
244
+ end
245
+
246
+
247
+ ##
248
+ # Used internaly. Use Roctave.fft instead of calling directly this function.
249
+ # Implements the Cooley-Tukey FFT
250
+ def self.iterative_ifft (arr_in)
251
+ ## Implementation from https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm 2019-06-11
252
+ n = arr_in.length
253
+ nbl = Math.log2(n)
254
+ raise ArgumentError.new "Input array must have a length power of two" if nbl.floor != nbl
255
+ nbl = nbl.to_i
256
+
257
+ ## bit-reverse copy
258
+ arr = (0...n).collect do |i|
259
+ arr_in[i.to_s(2).rjust(nbl, '0').reverse.to_i(2)]
260
+ end
261
+
262
+ (1..nbl).each do |s|
263
+ m = 2**s
264
+ wm = Complex.polar(1.0, 2.0*Math::PI/m)
265
+ (0...n).step(m).each do |k|
266
+ w = Complex(1.0)
267
+ (0 ... m/2).each do |j|
268
+ t = w*arr[k+j+m/2]
269
+ u = arr[k+j]
270
+ arr[k+j] = u + t
271
+ arr[k+j+m/2] = u - t
272
+ w = w * wm
273
+ end
274
+ end
275
+ end
276
+
277
+ arr
278
+ end
279
+ end
280
+
@@ -0,0 +1,64 @@
1
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
2
+ ##
3
+ ## This file is part of Roctave
4
+ ##
5
+ ## Roctave is free software: you can redistribute it and/or modify
6
+ ## it under the terms of the GNU General Public License as published by
7
+ ## the Free Software Foundation, either version 3 of the License, or
8
+ ## (at your option) any later version.
9
+ ##
10
+ ## Roctave is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+ ##
15
+ ## You should have received a copy of the GNU General Public License
16
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ module Roctave
20
+ module Filter
21
+ # @param flt [Roctave::FirFilter, Roctave::IirFilter]
22
+ # @return [Roctave::FirFilter, Roctave::IirFilter]
23
+ def cascade (flt)
24
+ my_b = numerator
25
+ my_a = denominator
26
+
27
+ his_b = flt.numerator
28
+ his_a = flt.denominator
29
+
30
+ c = my_b
31
+ d = his_b
32
+ len = c.length + d.length - 1
33
+ d = len.times.collect do |i|
34
+ ([0, i - d.length + 1].max .. [c.length - 1, i].min).inject(0.0){|sum, k| sum + c[k]*d[i-k]}
35
+ end
36
+ b = d
37
+
38
+ c = my_a
39
+ d = his_a
40
+ len = c.length + d.length - 1
41
+ d = len.times.collect do |i|
42
+ ([0, i - d.length + 1].max .. [c.length - 1, i].min).inject(0.0){|sum, k| sum + c[k]*d[i-k]}
43
+ end
44
+ a = d
45
+
46
+ b.collect!{|v| v/ a[0]}
47
+ a.collect!{|v| v/ a[0]}
48
+
49
+ fir = true
50
+ a[1..-1].each do |v|
51
+ fir = false if v != 0.0
52
+ end
53
+
54
+ if fir then
55
+ r = Roctave::FirFilter.new(b)
56
+ else
57
+ r = Roctave::IirFilter.new(b, a)
58
+ end
59
+
60
+ r
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,73 @@
1
+ ## Copyright (C) 2000 Paul Kienzle <pkienzle@users.sf.net> (GNU Octave implementation)
2
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
3
+ ##
4
+ ## This file is part of Roctave
5
+ ##
6
+ ## Roctave is free software: you can redistribute it and/or modify
7
+ ## it under the terms of the GNU General Public License as published by
8
+ ## the Free Software Foundation, either version 3 of the License, or
9
+ ## (at your option) any later version.
10
+ ##
11
+ ## Roctave is distributed in the hope that it will be useful,
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ ## GNU General Public License for more details.
15
+ ##
16
+ ## You should have received a copy of the GNU General Public License
17
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
18
+
19
+
20
+ module Roctave
21
+ # @!group FIR filters
22
+
23
+ ##
24
+ # Finite difference equations enable you to take derivatives of any order
25
+ # at any point using any given sufficiently large selection of points.
26
+ # By inputting the locations of your sampled points,
27
+ # this function generates a finite difference equation which will
28
+ # approximate the derivative at any desired location.
29
+ #
30
+ # Taken from
31
+ # https://en.wikipedia.org/wiki/Finite_difference_coefficient
32
+ # and
33
+ # http://web.media.mit.edu/~crtaylor/calculator.html
34
+ #
35
+ # @param s [Array<Float>, Range] Location of sample points, given as an array ex: [-3, -1, 0, 2], or a range ex: (-2 ... 1) which is equivalent to [-2, -1, 0, 1]
36
+ # @param d [Integer] Derivative order
37
+ # @param fs [Float] Sampling frequency
38
+ # @return [Array<Float>] The coefficients
39
+ def self.finite_difference_coefficients (s, d = 1, fs = 1.0)
40
+ case s
41
+ when Range
42
+ s = Range.new([s.begin, s.end].min.to_i, [s.begin, s.end].max.to_i).to_a.collect{|e| e.to_f}
43
+ when Array
44
+ s = s.collect{|e| e.to_f}.sort.uniq
45
+ else
46
+ raise ArgumentError.new "Location of sample points must be an array of integers, or a range"
47
+ end
48
+ d = [1, d.to_i].max
49
+ fs = fs.to_f
50
+ len = s.length
51
+ raise ArgumentError.new "Please enter a derivative order that is less than the number of points in your stencil." if d >= len
52
+
53
+ y = Matrix.build(len, 1) do |row, col|
54
+ if row == d then
55
+ ## d! / h^d
56
+ (1..d).inject(&:*) * fs**d
57
+ else
58
+ 0.0
59
+ end
60
+ end
61
+
62
+ a = Matrix.build(len) do |row, col|
63
+ s[col]**row
64
+ end
65
+
66
+ a_inv = a.inverse
67
+
68
+ x = a_inv * y
69
+
70
+ x.to_a.flatten.reverse
71
+ end
72
+ end
73
+