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,311 @@
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 FIR filters
21
+
22
+ ##
23
+ # @param n [Integer] The filter order
24
+ # @param f [Float] Cutoff frequency in the range [0 1] maping to w = [0 pi]
25
+ # @param window [Array<Float>, Symbol] The window to use, either an array of length N+1, or a symbol. Defaults to :hamming
26
+ # @return [Array<Float>] The filter coefficients B
27
+ def self.fir_low_pass (n, f, window: :hamming)
28
+ f = [0.0, [1.0, f.to_f].min].max
29
+ n = [1, n.to_i].max
30
+ len = n + 1
31
+
32
+ case window
33
+ when Array
34
+ raise ArgumentError.new "Window array must be of length #{len}" unless window.length == len
35
+ when Symbol
36
+ window = self.send(window, len)
37
+ when nil
38
+ else
39
+ raise ArgumentError.new "Window must either be an array of length N+1 or the symbol of a window"
40
+ end
41
+
42
+ f *= Math::PI
43
+ b = (0...len).collect do |i|
44
+ j = i - n/2.0
45
+ if j == 0.0 then
46
+ f / Math::PI
47
+ else
48
+ Math.sin(f*j) / (Math::PI*j)
49
+ end
50
+ end
51
+
52
+ b.collect!.with_index{|e, i| e*window[i]} if window
53
+
54
+ b
55
+ end
56
+
57
+
58
+ ##
59
+ # @param n [Integer] The filter order, must be even
60
+ # @param f [Float] Cutoff frequency in the range [0 1] maping to w = [0 pi]
61
+ # @param window [Array<Float>, Symbol] The window to use, either an array of length N+1, or a symbol. Defaults to :hamming
62
+ # @return [Array<Float>] The filter coefficients B
63
+ def self.fir_high_pass (n, f, window: :hamming)
64
+ f = [0.0, [1.0, f.to_f].min].max
65
+ n = [1, n.to_i].max
66
+ if (n & 1) != 0 then
67
+ n += 1
68
+ STDERR.puts "WARNING: #{self.class}::#{__method__}() Order must be even, incrementing to #{n}"
69
+ end
70
+ len = n + 1
71
+
72
+ case window
73
+ when Array
74
+ raise ArgumentError.new "Window array must be of length #{len}" unless window.length == len
75
+ when Symbol
76
+ window = self.send(window, len)
77
+ when nil
78
+ else
79
+ raise ArgumentError.new "Window must either be an array of length N+1 or the symbol of a window"
80
+ end
81
+
82
+ f *= Math::PI
83
+ b = (0...len).collect do |i|
84
+ j = i - n/2.0
85
+ if j == 0.0 then
86
+ 1.0 - f / Math::PI
87
+ else
88
+ -Math.sin(f*j) / (Math::PI*j)
89
+ end
90
+ end
91
+
92
+ b.collect!.with_index{|e, i| e*window[i]} if window
93
+
94
+ b
95
+ end
96
+
97
+
98
+ ##
99
+ # @param n [Integer] The filter order, must be even
100
+ # @param f [Float, Array<Float, Float>, Range] Either a two elements array or a range defining the band edges, or the center frequency (in that case +width+ must be specified). Frequencies must be the range [0 1] maping to w = [0 pi]
101
+ # @param width [Float] Width around the ceter frequency +f+, maping to w = [0 pi]
102
+ # @param window [Array<Float>, Symbol] The window to use, either an array of length N+1, or a symbol. Defaults to :hamming
103
+ # @return [Array<Float>] The filter coefficients B
104
+ def self.fir_band_stop (n, f, width = nil, window: :hamming)
105
+ if f.kind_of?(Array) then
106
+ raise ArgumentError.new "Second element must be a two element array containing band edges or the center frequency" unless f.length == 2
107
+ f1 = f.first
108
+ f2 = f.last
109
+ raise ArgumentError.new "Width around the center frequeny is not used when band edges are specified with an array" unless width.nil?
110
+ elsif f.kind_of?(Range) then
111
+ f1 = f.begin
112
+ f2 = f.end
113
+ raise ArgumentError.new "Width around the center frequeny is not used when band edges are specified with an range" unless width.nil?
114
+ elsif f.kind_of?(Numeric) then
115
+ raise ArgumentError.new "You must specify the Width around the center frequeny when the center frequency is specified as a scalar" unless width.kind_of?(Float)
116
+ f = [0.0, [1.0, f.to_f].min].max
117
+ width = [0.0, [2.0, width.to_f].min].max / 2.0
118
+ f1 = f - width
119
+ f2 = f + width
120
+ else
121
+ raise ArgumentError.new "Second argument must be a scalar, an array or a range"
122
+ end
123
+
124
+ f1 = [0.0, [1.0, f1.to_f].min].max
125
+ f2 = [0.0, [1.0, f2.to_f].min].max
126
+ f1,f2 = f2,f1 if f1 > f2
127
+ n = [1, n.to_i].max
128
+ if (n & 1) != 0 then
129
+ n += 1
130
+ STDERR.puts "WARNING: #{self.class}::#{__method__}() Order must be even, incrementing to #{n}"
131
+ end
132
+ len = n + 1
133
+
134
+ case window
135
+ when Array
136
+ raise ArgumentError.new "Window array must be of length #{len}" unless window.length == len
137
+ when Symbol
138
+ window = self.send(window, len)
139
+ when nil
140
+ else
141
+ raise ArgumentError.new "Window must either be an array of length N+1 or the symbol of a window"
142
+ end
143
+
144
+ bl = Roctave.fir_low_pass(n, f1, window: window)
145
+ bh = Roctave.fir_high_pass(n, f2, window: window)
146
+
147
+ (0...len).collect{|i| bl[i] + bh[i]}
148
+ end
149
+
150
+
151
+ ##
152
+ # @param n [Integer] The filter order, must be even
153
+ # @param f [Float, Array<Float, Float>, Range] Either a two elements array or a range defining the band edges, or the center frequency (in that case +width+ must be specified). Frequencies must be the range [0 1] maping to w = [0 pi]
154
+ # @param width [Float] Width around the ceter frequency +f+, maping to w = [0 pi]
155
+ # @param window [Array<Float>, Symbol] The window to use, either an array of length N+1, or a symbol. Defaults to :hamming
156
+ # @return [Array<Float>] The filter coefficients B
157
+ def self.fir_band_pass (n, f, width = nil, window: :hamming)
158
+ if f.kind_of?(Array) then
159
+ raise ArgumentError.new "Second element must be a two element array containing band edges or the center frequency" unless f.length == 2
160
+ f1 = f.first
161
+ f2 = f.last
162
+ raise ArgumentError.new "Width around the center frequeny is not used when band edges are specified with an array" unless width.nil?
163
+ elsif f.kind_of?(Range) then
164
+ f1 = f.begin
165
+ f2 = f.end
166
+ raise ArgumentError.new "Width around the center frequeny is not used when band edges are specified with an range" unless width.nil?
167
+ elsif f.kind_of?(Numeric) then
168
+ raise ArgumentError.new "You must specify the Width around the center frequeny when the center frequency is specified as a scalar" unless width.kind_of?(Float)
169
+ f = [0.0, [1.0, f.to_f].min].max
170
+ width = [0.0, [2.0, width.to_f].min].max / 2.0
171
+ f1 = f - width
172
+ f2 = f + width
173
+ else
174
+ raise ArgumentError.new "Second argument must be a scalar, an array or a range"
175
+ end
176
+
177
+ f1 = [0.0, [1.0, f1.to_f].min].max
178
+ f2 = [0.0, [1.0, f2.to_f].min].max
179
+ f1,f2 = f2,f1 if f1 > f2
180
+ n = [1, n.to_i].max
181
+ parity = ((n & 1) == 0) ? :even : :odd
182
+ len = n + 1
183
+
184
+ case window
185
+ when Array
186
+ raise ArgumentError.new "Window array must be of length #{len}" unless window.length == len
187
+ when Symbol
188
+ window = self.send(window, len)
189
+ when nil
190
+ else
191
+ raise ArgumentError.new "Window must either be an array of length N+1 or the symbol of a window"
192
+ end
193
+
194
+ if parity == :even then
195
+ b = Roctave.fir_band_stop(n, [f1, f2], window: window)
196
+ b.collect!.with_index do |e, i|
197
+ r = -e
198
+ r += 1 if i == n / 2.0
199
+ r
200
+ end
201
+ else
202
+ bl = Roctave.fir_low_pass(n, f1, window: window)
203
+ bh = Roctave.fir_low_pass(n, f2, window: window)
204
+
205
+ b = (0...len).collect{|i| bh[i] - bl[i]}
206
+ end
207
+
208
+ b
209
+ end
210
+
211
+
212
+ def self.fir_differentiator (n, window: :blackman)
213
+ n = [2, n.to_i].max
214
+ if (n & 1) != 0 then
215
+ n += 1
216
+ STDERR.puts "WARNING: #{self.name}::#{__method__}() Forcing type III FIR filter, order must be even, incrementing to #{n}."
217
+ end
218
+ len = n + 1
219
+
220
+ case window
221
+ when Array
222
+ raise ArgumentError.new "Window array must be of length #{len}" unless window.length == len
223
+ when Symbol
224
+ window = self.send(window, len)
225
+ when nil
226
+ else
227
+ raise ArgumentError.new "Window must either be an array of length N+1 or the symbol of a window"
228
+ end
229
+
230
+ b = (-n/2 .. n/2).collect do |i|
231
+ if i == 0 then
232
+ 0.0
233
+ else
234
+ (-1)**i / i.to_f
235
+ end
236
+ end
237
+
238
+ b.collect!.with_index{|e, i| e*window[i]} if window
239
+
240
+ b
241
+ end
242
+
243
+
244
+ def self.fir_hilbert (n, window: :hamming)
245
+ n = [2, n.to_i].max
246
+ if (n & 1) != 0 then
247
+ n += 1
248
+ STDERR.puts "WARNING: #{self.name}::#{__method__}() Forcing type III FIR filter, order must be even, incrementing to #{n}."
249
+ end
250
+ len = n + 1
251
+
252
+ case window
253
+ when Array
254
+ raise ArgumentError.new "Window array must be of length #{len}" unless window.length == len
255
+ when Symbol
256
+ window = self.send(window, len)
257
+ when nil
258
+ else
259
+ raise ArgumentError.new "Window must either be an array of length N+1 or the symbol of a window"
260
+ end
261
+
262
+ b = (-n/2 .. n/2).collect do |i|
263
+ if i == 0 then
264
+ 0.0
265
+ else
266
+ (1 - (-1)**i) / (Math::PI * i)
267
+ end
268
+ end
269
+
270
+ b.collect!.with_index{|e, i| e*window[i]} if window
271
+
272
+ b
273
+ end
274
+
275
+
276
+ ##
277
+ # Not very usefull, not better than fir_differentiator()
278
+ # @param fc [Float] Normalized cutoff frequency in the range [0 0.5]
279
+ def self.fir_differentiator_low_pass (n, fc = 0.5, window: :hamming)
280
+ fc = [0.0, [0.5, fc.to_f].min].max
281
+ n = [2, n.to_i].max
282
+ if (n & 1) != 0 then
283
+ n += 1
284
+ STDERR.puts "WARNING: #{self.name}::#{__method__}() Forcing type III FIR filter, order must be even, incrementing to #{n}."
285
+ end
286
+ len = n + 1
287
+
288
+ case window
289
+ when Array
290
+ raise ArgumentError.new "Window array must be of length #{len}" unless window.length == len
291
+ when Symbol
292
+ window = self.send(window, len)
293
+ when nil
294
+ else
295
+ raise ArgumentError.new "Window must either be an array of length N+1 or the symbol of a window"
296
+ end
297
+
298
+ b = (-n/2 .. n/2).collect do |i|
299
+ if i == 0 then
300
+ 0.0
301
+ else
302
+ -2.0/(Math::PI*i*i)*Math.sin(Math::PI*i*fc)*(1.0 - Math.cos(Math::PI*i*fc))
303
+ end
304
+ end
305
+
306
+ b.collect!.with_index{|e, i| e*window[i]} if window
307
+
308
+ b
309
+ end
310
+ end
311
+
@@ -0,0 +1,380 @@
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
+ # class Matrix
20
+ # def puts
21
+ # strings = Array.new(self.row_size){Array.new(self.column_size)}
22
+ # (0 ... self.row_size).each do |i|
23
+ # (0 ... self.column_size).each do |j|
24
+ # strings[i][j] = self[i, j].round(4).to_s
25
+ # end
26
+ # end
27
+ # maxstrlen = strings.flatten.collect{|s| s.length + 2}.max
28
+ # Kernel.puts
29
+ # (0 ... self.row_size).each do |i|
30
+ # if i == 0 and i == self.row_size - 1 then
31
+ # print ' ['
32
+ # elsif i == 0 then
33
+ # print ' ⎡'
34
+ # elsif i == self.row_size - 1 then
35
+ # print ' ⎣'
36
+ # else
37
+ # print ' ⎢'
38
+ # end
39
+ #
40
+ # (0 ... self.column_size).each do |j|
41
+ # print strings[i][j].center(maxstrlen)
42
+ # end
43
+ #
44
+ # if i == 0 and i == self.row_size - 1 then
45
+ # Kernel.puts ']'
46
+ # elsif i == 0 then
47
+ # Kernel.puts '⎤'
48
+ # elsif i == self.row_size - 1 then
49
+ # Kernel.puts '⎦'
50
+ # else
51
+ # Kernel.puts '⎥'
52
+ # end
53
+ # end
54
+ # Kernel.puts
55
+ # end
56
+ # end
57
+
58
+
59
+ module Roctave
60
+ # @!group FIR filters
61
+
62
+ ##
63
+ # Weighted least-squares method for FIR filter approximation by optimization.
64
+ # Minimizes the square of the energy of the error function.
65
+ # @param n [Integer] The filter order
66
+ # @param f [Array<Float, Array<Float>, Range>] Frequency band edges, onced flattened, must be of even length
67
+ # @param a [Array<Float, Array<Float>, Range>] Magnitude at frequency bands. Each element must be a scalar (for the whole band), or a range or a two element array (for the two band edges of the band). Length +a+ == length +f+ / 2
68
+ # @param w [Array<Float, Array<Float>, Range] Weight at frequency bands. Each element must be a scalar (for the whole band), or a range or a two element array (for the two band edges of the band). Length +w+ == length +f+ / 2
69
+ # @param type [:odd_symmetry, :even_symmetry] Specify the symmetry of the filter. nil and :even_symmetry are the same and default, for type 1 and 2 filters, :odd_symmetry is for type 3 and 4 filters.
70
+ # @param grid [Integer] The length of the grid over which the frequency response is evaluated. Defaults to 16, for an interpolation on 16*+n+.
71
+ # @return [Array<Float>] The filter coefficients B
72
+ def self.firls (n, f, a, *args)
73
+ raise ArgumentError.new "Expecting no more than 6 arguments" if args.length > 3
74
+
75
+ n = [1, n.to_i].max
76
+
77
+ case f
78
+ when Array
79
+ when Range
80
+ f = [f]
81
+ else
82
+ raise ArgumentError.new "F must be an array or a range"
83
+ end
84
+ f.collect! do |e|
85
+ case e
86
+ when Range
87
+ [e.begin.to_f, e.end.to_f]
88
+ when Array
89
+ raise ArgumentError.new "Elements of F which are arrays must have exaclty two floats" unless (e.length == 2 and e.first.kind_of?(Float) and e.last.kind_of?(Float))
90
+ [e.first.to_f, e.last.to_f]
91
+ else
92
+ e.to_f
93
+ end
94
+ end
95
+ f = f.flatten
96
+ raise ArgumentError.new "At least one frequency band must be specified!" if f.empty?
97
+ raise ArgumentError.new "Once flattened, F must have an even number of frequency bands!" unless (f.length & 1) == 0
98
+ raise ArgumentError.new "F must have at least one band!" unless f.length >= 2
99
+ f.each do |e|
100
+ raise ArgumentError.new "Frequency band edges must be in the range [0, 1]" unless (e >= 0.0 and e <= 1.0)
101
+ end
102
+ (1...f.length).each do |i|
103
+ raise ArgumentError.new "Frequecy band edges must be strictly increasing" if f[i-1] >= f[i]
104
+ end
105
+
106
+ case a
107
+ when Array
108
+ when Range
109
+ [a]
110
+ when Numeric
111
+ [a]
112
+ else
113
+ raise ArgumentError.new "A must be an array or a range"
114
+ end
115
+ raise ArgumentError.new "Length of A must be half the length of F" unless a.length == f.length/2
116
+ a.collect! do |e|
117
+ case e
118
+ when Range
119
+ [e.begin.to_f.abs, e.end.to_f.abs]
120
+ when Array
121
+ raise ArgumentError.new "Elements of A which are arrays must have at least two floats" if e.length < 2
122
+ e.collect{|v| v.to_f}
123
+ else
124
+ [e.to_f.abs, e.to_f.abs]
125
+ end
126
+ end
127
+
128
+ w = nil
129
+ type = :even_symmetry
130
+ grid = 16
131
+
132
+ args.each.with_index do |arg, i|
133
+ case arg
134
+ when :even_symmetry
135
+ type = :even_symmetry
136
+ when :odd_symmetry
137
+ type = :odd_symmetry
138
+ when Numeric
139
+ grid = [1, arg.to_i].max
140
+ STDERR.puts "WARNING: recommended grid sampling factor are integers in the range [8, 16]." unless Range.new(8, 16).include?(grid)
141
+ when Array
142
+ raise ArgumentError.new "Length of W must be half the length of F" unless arg.length == f.length/2
143
+ w = arg.collect do |e|
144
+ case e
145
+ when Range
146
+ [e.begin.to_f.abs, e.end.to_f.abs]
147
+ when Array
148
+ raise ArgumentError.new "Elements of W which are arrays must have at least two floats" if e.length < 2
149
+ e.collect{|v| v.to_f}
150
+ else
151
+ [e.to_f.abs, e.to_f.abs]
152
+ end
153
+ end
154
+ else
155
+ raise ArgumentError.new "Argument #{i+4} must either be :odd_symmetry, :even_symmetry, an array of weight or the grid sampling (Integer)"
156
+ end
157
+ end
158
+
159
+ w = (f.length / 2).times.collect{[1.0, 1.0]} if w.nil?
160
+
161
+ bands = (0 ... a.length).collect do |i|
162
+ {
163
+ frequencies: Range.new(f[2*i], f[2*i+1]),
164
+ amplitudes: a[i],
165
+ weights: w[i]
166
+ }
167
+ end
168
+
169
+ ## Create the sampling grid by interpolation ##
170
+ nb_sample_points = grid*n
171
+ constrained_frequency_amount = bands.inject(0.0){|m, h| m + h[:frequencies].end - h[:frequencies].begin}
172
+ # debug_amp_plot_args = []
173
+ # debug_weight_plot_args = []
174
+ bands.each do |band|
175
+ # debug_amp_plot_args << Roctave.linspace(band[:frequencies].begin, band[:frequencies].end, band[:amplitudes].length)
176
+ # debug_amp_plot_args << band[:amplitudes].collect{|e| e}
177
+ # debug_amp_plot_args << '-+b'
178
+ # debug_weight_plot_args << Roctave.linspace(band[:frequencies].begin, band[:frequencies].end, band[:weights].length)
179
+ # debug_weight_plot_args << band[:weights].collect{|e| e}
180
+ # debug_weight_plot_args << '-+b'
181
+
182
+ fi = Roctave.linspace(band[:frequencies].begin, band[:frequencies].end, [2, ((band[:frequencies].end - band[:frequencies].begin) * nb_sample_points / constrained_frequency_amount).ceil].max)
183
+ ai = Roctave.interp1(Roctave.linspace(band[:frequencies].begin, band[:frequencies].end, band[:amplitudes].length), band[:amplitudes], fi)
184
+ wi = Roctave.interp1(Roctave.linspace(band[:frequencies].begin, band[:frequencies].end, band[:weights].length), band[:weights], fi)
185
+
186
+ # debug_amp_plot_args << fi.collect{|e| e}
187
+ # debug_amp_plot_args << ai.collect{|e| e}
188
+ # debug_amp_plot_args << '-xr'
189
+ # debug_weight_plot_args << fi.collect{|e| e}
190
+ # debug_weight_plot_args << wi.collect{|e| e}
191
+ # debug_weight_plot_args << '-xr'
192
+
193
+ band[:frequencies] = fi.collect{|v| v*Math::PI}
194
+ band[:amplitudes] = ai
195
+ band[:weights] = wi
196
+ end
197
+ nb_sample_points = bands.inject(0.0){|m, h| m + h[:frequencies].length}
198
+
199
+
200
+ ## Filter type ##
201
+ if type == :even_symmetry then
202
+ if (n & 1) == 0 then
203
+ filter_type = 1
204
+ beta = 0.0
205
+ q = -> w { 1.0 }
206
+ l = n / 2
207
+ coefs_from_p = -> p {
208
+ (-l .. l).collect{|m| m.abs}.collect do |m|
209
+ if m == 0 then
210
+ p[m, 0]
211
+ else
212
+ p[m, 0] / 2.0
213
+ end
214
+ end
215
+ }
216
+ else
217
+ filter_type = 2
218
+ beta = 0.0
219
+ q = -> w { Math.cos(w/2.0) }
220
+ l = (n - 1) / 2
221
+ coefs_from_p = -> p {
222
+ id = (1..l+1).to_a
223
+ (id.reverse + id).collect{ |m|
224
+ if m == 1 then
225
+ p[0, 0] + 0.5*p[1, 0]
226
+ elsif m == l+1 then
227
+ 0.5*p[l, 0]
228
+ else
229
+ 0.5*(p[m-1, 0] + p[m, 0])
230
+ end
231
+ }.collect{|v| v/2.0}
232
+ }
233
+ end
234
+ else
235
+ if (n & 1) == 0 then
236
+ filter_type = 3
237
+ beta = Math::PI/2
238
+ q = -> w { Math.sin(w) }
239
+ l = n / 2 - 1
240
+ coefs_from_p = -> p {
241
+ id = (1..l+1).to_a
242
+ (id.reverse + [0] + id).collect{ |m|
243
+ if m == 0 then
244
+ 0.0
245
+ elsif m == 1 then
246
+ p[0, 0] - 0.5*p[2, 0]
247
+ elsif m >= l then
248
+ 0.5*p[m-1, 0]
249
+ else
250
+ 0.5*(p[m-1,0] - p[m+1, 0])
251
+ end
252
+ }.collect.with_index{ |v, i|
253
+ if i < l + 1 then
254
+ v/2.0
255
+ else
256
+ -v/2.0
257
+ end
258
+ }
259
+ }
260
+ else
261
+ filter_type = 4
262
+ beta = Math::PI/2
263
+ q = -> w { Math.sin(w/2.0) }
264
+ l = (n - 1) / 2
265
+ coefs_from_p = -> p {
266
+ id = (1..l+1).to_a
267
+ (id.reverse + id).collect{ |m|
268
+ if m == 1 then
269
+ p[0, 0] - 0.5*p[1, 0]
270
+ elsif m == l+1 then
271
+ 0.5*p[l, 0]
272
+ else
273
+ 0.5*(p[m-1, 0] - p[m, 0])
274
+ end
275
+ }.collect.with_index{ |v, i|
276
+ if i < l + 1 then
277
+ v/2.0
278
+ else
279
+ -v/2.0
280
+ end
281
+ }
282
+ }
283
+ end
284
+ end
285
+ # puts "Filter Type #{case filter_type; when 1 then 'I'; when 2 then 'II'; when 3 then 'III'; else 'IV' end}"
286
+
287
+
288
+ ## Create the matrices ##
289
+ frequencies = bands.inject([]){|m, h| m + h[:frequencies]}
290
+ amplitudes = bands.inject([]){|m, h| m + h[:amplitudes]}
291
+ weights = bands.inject([]){|m, h| m + h[:weights]}
292
+
293
+ mat_Wq2 = Matrix.build(nb_sample_points) do |i, j|
294
+ if i == j then
295
+ ((weights[i] * q.call(frequencies[i]))**2).to_f
296
+ #Rational((weights[i] * q.call(frequencies[i]))**2)
297
+ else
298
+ 0
299
+ #Rational(0)
300
+ end
301
+ end
302
+
303
+ mat_dq = Matrix.build(nb_sample_points, 1) do |i|
304
+ qw = q.call(frequencies[i])
305
+ qw = 0.000000001 if qw == 0.0
306
+ amplitudes[i] / qw
307
+ #Rational(amplitudes[i] / qw)
308
+ end
309
+
310
+ mat_U = Matrix.build(nb_sample_points, l + 1) do |i, j|
311
+ Math.cos(j * frequencies[i])
312
+ #Rational(Math.cos(j * frequencies[i]))
313
+ end
314
+
315
+ if false then
316
+ mat_UT_times_mat_Wq2 = mat_U.transpose * mat_Wq2
317
+ p = ((mat_UT_times_mat_Wq2 * mat_U).inverse * (mat_UT_times_mat_Wq2 * mat_dq)).collect{|e| e.to_f}
318
+ else
319
+ ts_whole = Time.now
320
+ puts "go"
321
+ #ts_inv = Time.now
322
+ #mat_ut = mat_U.transpose
323
+ #puts "time transp: #{(Time.now - ts_inv).round(3)}"
324
+ #mat_ut.puts
325
+ #mat_Wq2.puts
326
+ ts_inv = Time.now
327
+ #mat_UT_times_mat_Wq2 = mat_ut * mat_Wq2
328
+ mat_UT_times_mat_Wq2 = Matrix.build(l+1, nb_sample_points) do |i, j|
329
+ mat_U[j, i] * mat_Wq2[j, j]
330
+ end
331
+ puts "time mult1: #{(Time.now - ts_inv).round(3)}"
332
+ #mat_UT_times_mat_Wq2.puts
333
+ ts_inv = Time.now
334
+ mat_truc = mat_UT_times_mat_Wq2 * mat_U
335
+ puts "time mult1.5: #{(Time.now - ts_inv).round(3)}"
336
+ ts_inv = Time.now
337
+ mat_inv = mat_truc.inverse
338
+ puts "time inv: #{(Time.now - ts_inv).round(3)}"
339
+ ts_inv = Time.now
340
+ mat_truc = mat_UT_times_mat_Wq2 * mat_dq
341
+ puts "time mult2: #{(Time.now - ts_inv).round(3)}"
342
+ ts_inv = Time.now
343
+ p = mat_inv * mat_truc
344
+ puts "time mult3: #{(Time.now - ts_inv).round(3)}"
345
+ p = p.collect{|e| e.to_f}
346
+ puts "time tot: #{(Time.now - ts_whole).round(3)}"
347
+ end
348
+
349
+ #p.puts
350
+
351
+ b = coefs_from_p.call(p)
352
+
353
+ ###
354
+ # Matrix.column_vector(b).puts
355
+ #
356
+ # if filter_type == 1 or filter_type == 3 then
357
+ # Roctave.stem((-n/2..n/2).to_a, b, :filled, grid: true)
358
+ # else
359
+ # Roctave.stem((n+1).times.collect{|i| i - (n+1)/2.0 + 0.5}, b, :filled, grid: true)
360
+ # end
361
+ #
362
+ # r2 = Roctave.dft(b, 2048)
363
+ # h2 = r2[0...1024].abs
364
+ # p2 = r2[0...1024].arg
365
+ # p2 = Roctave.unwrap(p2)
366
+ # w2 = Roctave.linspace(0, 1, 1024)
367
+ # debug_amp_plot_args << w2
368
+ # debug_amp_plot_args << h2
369
+ # debug_amp_plot_args << '-g;Response;'
370
+ #
371
+ # #Roctave.plot(*debug_weight_plot_args, title: 'Target weights', xlim: (0 .. 1), grid: true)
372
+ # Roctave.plot(*debug_amp_plot_args, title: 'Target filter magnitude response', xlim: (0 .. 1), grid: true)
373
+ # Roctave.plot(w2, h2.collect{|v| 10*Math.log10(v)}, xlabel: 'Normalized frequency', ylabel: 'Magnitude response (dB)', grid: true, title: 'Magnitude')
374
+ # Roctave.plot(w2, p2, grid: true, title: 'Phase')
375
+ ###
376
+
377
+ b
378
+ end
379
+ end
380
+