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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +33 -0
- data/ext/roctave/cu8_file_reader.c +331 -0
- data/ext/roctave/cu8_file_reader.h +30 -0
- data/ext/roctave/extconf.rb +6 -0
- data/ext/roctave/fir_filter.c +795 -0
- data/ext/roctave/fir_filter.h +29 -0
- data/ext/roctave/freq_shifter.c +410 -0
- data/ext/roctave/freq_shifter.h +29 -0
- data/ext/roctave/iir_filter.c +462 -0
- data/ext/roctave/iir_filter.h +29 -0
- data/ext/roctave/roctave.c +38 -0
- data/ext/roctave/roctave.h +27 -0
- data/lib/roctave.rb +168 -0
- data/lib/roctave/bilinear.rb +92 -0
- data/lib/roctave/butter.rb +87 -0
- data/lib/roctave/cheby.rb +180 -0
- data/lib/roctave/cu8_file_reader.rb +45 -0
- data/lib/roctave/dft.rb +280 -0
- data/lib/roctave/filter.rb +64 -0
- data/lib/roctave/finite_difference_coefficients.rb +73 -0
- data/lib/roctave/fir.rb +121 -0
- data/lib/roctave/fir1.rb +134 -0
- data/lib/roctave/fir2.rb +246 -0
- data/lib/roctave/fir_design.rb +311 -0
- data/lib/roctave/firls.rb +380 -0
- data/lib/roctave/firpm.rb +499 -0
- data/lib/roctave/freq_shifter.rb +47 -0
- data/lib/roctave/freqz.rb +233 -0
- data/lib/roctave/iir.rb +80 -0
- data/lib/roctave/interp1.rb +78 -0
- data/lib/roctave/plot.rb +748 -0
- data/lib/roctave/poly.rb +46 -0
- data/lib/roctave/roots.rb +73 -0
- data/lib/roctave/sftrans.rb +157 -0
- data/lib/roctave/version.rb +3 -0
- data/lib/roctave/window.rb +116 -0
- data/roctave.gemspec +79 -0
- data/samples/butter.rb +12 -0
- data/samples/cheby.rb +28 -0
- data/samples/dft.rb +18 -0
- data/samples/differentiator.rb +48 -0
- data/samples/differentiator_frequency_scaling.rb +52 -0
- data/samples/fft.rb +40 -0
- data/samples/finite_difference_coefficient.rb +53 -0
- data/samples/fir1.rb +13 -0
- data/samples/fir2.rb +14 -0
- data/samples/fir2_windows.rb +29 -0
- data/samples/fir2bank.rb +30 -0
- data/samples/fir_low_pass.rb +44 -0
- data/samples/firls.rb +77 -0
- data/samples/firpm.rb +78 -0
- data/samples/hilbert_transformer.rb +20 -0
- data/samples/hilbert_transformer_frequency_scaling.rb +47 -0
- data/samples/plot.rb +45 -0
- data/samples/stem.rb +8 -0
- data/samples/type1.rb +25 -0
- data/samples/type3.rb +24 -0
- data/samples/windows.rb +25 -0
- 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
|
+
|