roctave 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,29 @@
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
+
20
+ #ifndef IIR_FILTER_H
21
+ #define IIR_FILTER_H
22
+
23
+ #include <ruby.h>
24
+
25
+ extern VALUE c_IIR;
26
+
27
+ void Init_iir_filter();
28
+
29
+ #endif /* IIR_FILTER_H */
@@ -0,0 +1,38 @@
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
+
20
+ #include <ruby.h>
21
+ #include "roctave.h"
22
+ #include "fir_filter.h"
23
+ #include "iir_filter.h"
24
+ #include "cu8_file_reader.h"
25
+ #include "freq_shifter.h"
26
+
27
+ VALUE m_Roctave;
28
+
29
+ void Init_roctave()
30
+ {
31
+ m_Roctave = rb_const_get_at(rb_cObject, rb_intern("Roctave"));
32
+
33
+ Init_fir_filter();
34
+ Init_iir_filter();
35
+ Init_cu8_file_reader();
36
+ Init_freq_shifter();
37
+ }
38
+
@@ -0,0 +1,27 @@
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
+
20
+ #ifndef ROCTAVE_H
21
+ #define ROCTAVE_H
22
+
23
+ #include <ruby.h>
24
+
25
+ extern VALUE m_Roctave;
26
+
27
+ #endif /* ROCTAVE_H */
@@ -0,0 +1,168 @@
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
+ ##
21
+ # Return a row vector with +n+ linearly spaced elements between +base+
22
+ # and +limit+.
23
+ # @overload linspace(base, limit, nb_elem)
24
+ # @param base [Numeric]
25
+ # @param limit [Numeric]
26
+ # @param nb_elem [Integer] number of elements
27
+ # @return [Array<Numeric>]
28
+ # @overload linspace(range, nb_elem)
29
+ # @param range [Range]
30
+ # @param nb_elem [Integer] number of elements
31
+ # @return [Array<Numeric>]
32
+ def self.linspace (base, limit = nil, nb_elem)
33
+ nb_elem = [1, nb_elem.to_i].max
34
+ if limit.nil? then
35
+ limit = base.end
36
+ exclude = base.exclude_end?
37
+ base = base.begin
38
+ limit -= (limit - base) / nb_elem.to_f if exclude
39
+ end
40
+ return [base] if nb_elem == 1
41
+ nm1 = (nb_elem - 1).to_f
42
+ (0...nb_elem).collect do |i|
43
+ base + i*(limit - base) / nm1
44
+ end
45
+ end
46
+
47
+
48
+ # @param n [Integer] the lenght of the array
49
+ # @return [Array<Float>]
50
+ def self.zeros (len)
51
+ len = [0, len.to_i].max
52
+ Array.new(len){0.0}
53
+ end
54
+
55
+
56
+ # @param n [Integer] the lenght of the array
57
+ # @return [Array<Float>]
58
+ def self.ones (len)
59
+ len = [0, len.to_i].max
60
+ Array.new(len){1.0}
61
+ end
62
+
63
+ ##
64
+ # Unwrap radian phases by adding multiples of 2*pi as appropriate to
65
+ # remove jumps greater than TOL.
66
+ #
67
+ # TOL defaults to pi.
68
+ # @param x [Array<Float>] Input
69
+ # @param tol [Float] Threshold detection level
70
+ # @return [Array<Float>]
71
+ def self.unwrap (x, tol = Math::PI)
72
+ raise ArgumentError.new "First argument must be an array" unless x.kind_of?(Array)
73
+ tol = tol.to_f.abs
74
+ rng = 2.0*Math::PI
75
+ d = (1...x.length).collect{|i| x[i] - x[i-1]}
76
+ p = d.collect do |e|
77
+ v = (e.abs / rng).ceil * rng
78
+ if e > tol then
79
+ -v
80
+ elsif e < -tol then
81
+ v
82
+ else
83
+ 0.0
84
+ end
85
+ end
86
+ r = Array.new(x.length){0.0}
87
+ p.each.with_index do |e, i|
88
+ r[i+1] = r[i] + e
89
+ end
90
+ r.collect.with_index{|e, i| e + x[i]}
91
+ end
92
+
93
+
94
+ ##
95
+ # Append the scalar value C to the vector X until it is of length L.
96
+ # If C is not given, a value of 0 is used.
97
+ #
98
+ # If 'length (X) > L', elements from the end of X are removed until a
99
+ # vector of length L is obtained.<Paste>
100
+ # @param x [Array<Numeric>] Input array
101
+ # @param l [Integer] The requested length
102
+ # @param c [Numeric] Value to pad with (default: 0.0)
103
+ def self.postpad(x, l, c = 0.0)
104
+ raise ArgumentError.new "First argument must be an array" unless x.kind_of?(Array)
105
+ l = [0, l.to_i].max
106
+ len = x.length
107
+ if l == len then
108
+ x = x.collect{|v| v}
109
+ elsif l < len then
110
+ x = x[0...l]
111
+ else
112
+ x = x + Array.new(l - len){c}
113
+ end
114
+ x
115
+ end
116
+
117
+ end
118
+
119
+
120
+ class Array
121
+ def abs
122
+ self.collect(&:abs)
123
+ end
124
+
125
+ def real
126
+ self.collect(&:real)
127
+ end
128
+
129
+ def imag
130
+ self.collect(&:imag)
131
+ end
132
+
133
+ def arg
134
+ #self.collect{|e| e.kind_of?(Complex) ? e.arg : Complex(e).arg}
135
+ self.collect(&:arg)
136
+ end
137
+
138
+ def conj
139
+ self.collect(&:conj)
140
+ end
141
+ end
142
+
143
+ require 'matrix'
144
+ require_relative 'roctave/version.rb'
145
+ require_relative 'roctave/roctave'
146
+ require_relative 'roctave/window.rb'
147
+ require_relative 'roctave/dft.rb'
148
+ require_relative 'roctave/roots.rb'
149
+ require_relative 'roctave/plot.rb'
150
+ require_relative 'roctave/freqz.rb'
151
+ require_relative 'roctave/interp1.rb'
152
+ require_relative 'roctave/fir2.rb'
153
+ require_relative 'roctave/fir1.rb'
154
+ require_relative 'roctave/firls.rb'
155
+ require_relative 'roctave/firpm.rb'
156
+ require_relative 'roctave/fir_design.rb'
157
+ require_relative 'roctave/finite_difference_coefficients.rb'
158
+ require_relative 'roctave/sftrans.rb'
159
+ require_relative 'roctave/bilinear.rb'
160
+ require_relative 'roctave/poly.rb'
161
+ require_relative 'roctave/butter.rb'
162
+ require_relative 'roctave/cheby.rb'
163
+ require_relative 'roctave/filter.rb'
164
+ require_relative 'roctave/fir.rb'
165
+ require_relative 'roctave/iir.rb'
166
+ require_relative 'roctave/cu8_file_reader.rb'
167
+ require_relative 'roctave/freq_shifter.rb'
168
+
@@ -0,0 +1,92 @@
1
+ ## Copyright (C) 1999 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
+
22
+ ##
23
+ # Transform a s-plane filter specification into a z-plane
24
+ # specification. Filter is specified in zero-pole-gain form.
25
+ # 1/T is the sampling frequency represented in the z plane.
26
+ #
27
+ # Note: this differs from the bilinear function in the signal processing
28
+ # toolbox, which uses 1/T rather than T.
29
+ #
30
+ # Theory: Given a piecewise flat filter design, you can transform it
31
+ # from the s-plane to the z-plane while maintaining the band edges by
32
+ # means of the bilinear transform. This maps the left hand side of the
33
+ # s-plane into the interior of the unit circle. The mapping is highly
34
+ # non-linear, so you must design your filter with band edges in the
35
+ # s-plane positioned at 2/T tan(w*T/2) so that they will be positioned
36
+ # at w after the bilinear transform is complete.
37
+ #
38
+ # Please note that a pole and a zero at the same place exactly cancel.
39
+ # This is significant since the bilinear transform creates numerous
40
+ # extra poles and zeros, most of which cancel. Those which do not
41
+ # cancel have a "fill-in" effect, extending the shorter of the sets to
42
+ # have the same number of as the longer of the sets of poles and zeros
43
+ # (or at least split the difference in the case of the band pass
44
+ # filter). There may be other opportunistic cancellations but I will
45
+ # not check for them.
46
+ #
47
+ # Also note that any pole on the unit circle or beyond will result in
48
+ # an unstable filter. Because of cancellation, this will only happen
49
+ # if the number of poles is smaller than the number of zeros. The
50
+ # analytic design methods all yield more poles than zeros, so this will
51
+ # not be a problem.
52
+ #
53
+ # References:
54
+ # Proakis & Manolakis (1992). Digital Signal Processing. New York:
55
+ # Macmillan Publishing Company.
56
+ #
57
+ # @param sz [Array<Numeric>] Zeros in the S-plane
58
+ # @param sp [Array<Numeric>] Poles in the S-plane
59
+ # @param sg [Numeric] Gain in the S-plane
60
+ # @param t [Numeric] The sampling period in the Z-plane
61
+ # @return [Array<Array, Numeric>] An array containing zeros, poles and gain in the Z-plane [zz, zp, zg]
62
+ def self.bilinear (sz, sp, sg, t)
63
+ p = sp.length
64
+ z = sz.length
65
+
66
+ if z > p or p == 0 then
67
+ raise ArgumentError.new "bilinear: must have at least as many poles as zeros in s-plane"
68
+ end
69
+
70
+ ## ---------------- ------------------------- ------------------------ ##
71
+ ## Bilinear zero: (2+xT)/(2-xT) pole: (2+xT)/(2-xT) ##
72
+ ## 2 z-1 pole: -1 zero: -1 ##
73
+ ## S -> - --- gain: (2-xT)/T gain: (2-xT)/T ##
74
+ ## T z+1 ##
75
+ ## ---------------- ------------------------- ------------------------ ##
76
+ zg = (sg * sz.inject(1.0){|m, v| m * ((2.0-v*t)/t)} / sp.inject(1.0){|m, v| m * ((2.0-v*t)/t)}).real
77
+ zp = sp.collect{|v| (2.0+v*t)/(2.0-v*t)}
78
+ if sz.empty? then
79
+ zz = Array.new(p){-1.0}
80
+ else
81
+ zz = sz.collect{|v| (2.0+v*t)/(2.0-v*t)}
82
+ if zz.length > p then
83
+ zz = zz[0...p]
84
+ elsif zz.length < p then
85
+ zz = zz + Array.new(p - zz.length){-1.0}
86
+ end
87
+ end
88
+
89
+ return [zz, zp, zg]
90
+ end
91
+ end
92
+
@@ -0,0 +1,87 @@
1
+ ## Copyright (C) 1999 Paul Kienzle <pkienzle@users.sf.net> (GNU Octave implementation)
2
+ ## Copyright (C) 2003 Doug Stewart <dastew@sympatico.ca> (GNU Octave implementation)
3
+ ## Copyright (C) 2011 Alexander Klein <alexander.klein@math.uni-giessen.de> (GNU Octave implementation)
4
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
5
+ ##
6
+ ## This file is part of Roctave
7
+ ##
8
+ ## Roctave is free software: you can redistribute it and/or modify
9
+ ## it under the terms of the GNU General Public License as published by
10
+ ## the Free Software Foundation, either version 3 of the License, or
11
+ ## (at your option) any later version.
12
+ ##
13
+ ## Roctave is distributed in the hope that it will be useful,
14
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ ## GNU General Public License for more details.
17
+ ##
18
+ ## You should have received a copy of the GNU General Public License
19
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
20
+
21
+
22
+ module Roctave
23
+ # @!group IIR filters
24
+
25
+ ##
26
+ # Generate a Butterworth filter.
27
+ # @param n [Integer] Filter order
28
+ # @param w [Float, Array<Float, Float>, Range] Normalized to [0 1] cutoff frequency. Either a single frequency, or an array of two frequencies for a pass/stop-band or a range.
29
+ # @param type [Symbol] Either :low, :high, :pass, :stop
30
+ # @return [Array<Array{Float}>] An array containing the b and a arrays of floats [b, a].
31
+ def self.butter(n, w, type = :low)
32
+ n = [1, n.to_i].max
33
+
34
+ case w
35
+ when Numeric
36
+ w = [[0.0, [1.0, w.to_f].min].max]
37
+ when Range
38
+ w = [[0.0, [1.0, w.begin.to_f].min].max, [0.0, [1.0, w.end.to_f].min].max].sort
39
+ when Array
40
+ raise ArgumentError.new "Argument 2 must have exactly two elements" unless w.length == 2
41
+ w = [[0.0, [1.0, w.first.to_f].min].max, [0.0, [1.0, w.last.to_f].min].max].sort
42
+ else
43
+ raise ArgumentError.new "Argument 2 must be a numeric, a two numeric array or a range"
44
+ end
45
+
46
+ stop = false
47
+ case type
48
+ when :low
49
+ stop = false
50
+ when :pass
51
+ stop = false
52
+ when :high
53
+ stop = true
54
+ when :stop
55
+ stop = true
56
+ else
57
+ raise ArgumentError.new "Argument 3 must be :low, :pass, :high or :stop"
58
+ end
59
+
60
+ ## Prewarp to the band edges to s plane
61
+ t = 2.0 # sampling frequency of 2 Hz
62
+ w.collect!{|v| 2.0 / t * Math.tan(Math::PI * v / t)}
63
+
64
+ ## Generate splane poles for the prototype Butterworth filter
65
+ ## source: Kuc
66
+ c = 1.0 # default cutoff frequency
67
+ pole_s = (1..n).collect{|i| c * Complex.polar(1.0, Math::PI * (2.0 * i + n - 1) / (2 * n))}
68
+ if (n & 1) == 1 then
69
+ pole_s[(n + 1) / 2 - 1] = Complex(-1.0, 0.0) # pure real value at exp(i*pi)
70
+ end
71
+ zero_s = []
72
+ gain_s = (c**n).to_f;
73
+
74
+ ## S-plane frequency transform
75
+ zero_s, pole_s, gain_s = Roctave.sftrans(zero_s, pole_s, gain_s, w, stop)
76
+
77
+ ## Use bilinear transform to convert poles to the Z plane
78
+ zero_z, pole_z, gain_z = Roctave.bilinear(zero_s, pole_s, gain_s, t)
79
+
80
+ ## Convert to the correct output form
81
+ b = Roctave.poly(zero_z).collect{|v| (gain_z * v).real}
82
+ a = Roctave.poly(pole_z).collect{|v| v.real}
83
+
84
+ return [b, a]
85
+ end
86
+ end
87
+
@@ -0,0 +1,180 @@
1
+ ## Copyright (C) 1999 Paul Kienzle <pkienzle@users.sf.net> (GNU Octave implementation)
2
+ ## Copyright (C) 2003 Doug Stewart <dastew@sympatico.ca> (GNU Octave implementation)
3
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
4
+ ##
5
+ ## This file is part of Roctave
6
+ ##
7
+ ## Roctave is free software: you can redistribute it and/or modify
8
+ ## it under the terms of the GNU General Public License as published by
9
+ ## the Free Software Foundation, either version 3 of the License, or
10
+ ## (at your option) any later version.
11
+ ##
12
+ ## Roctave is distributed in the hope that it will be useful,
13
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ ## GNU General Public License for more details.
16
+ ##
17
+ ## You should have received a copy of the GNU General Public License
18
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
19
+
20
+
21
+ module Roctave
22
+ # @!group IIR filters
23
+
24
+ ##
25
+ # Generate a Chebyshev type I filter with rp dB of passband ripple.
26
+ # @param n [Integer] The filter order
27
+ # @param rp [Float] Peak-to-peak passband ripple. If positive, in dB, if negative, rp_dB = -20*log10(1-rp)
28
+ # @param wc [Float, Array<Float>, Range] The cutoff frequency normalized to [0 1], or the two band edges as an array or a range
29
+ # @param type [Symbol] Either :low, :high, :pass or :stop
30
+ # @return [Array<Array{Float}>] An array containing the numerator and denominator arrays of polynomial coefficients [b, a]
31
+ def self.cheby1 (n, rp, wc, type = :low)
32
+ n = [1, n.to_i].max
33
+
34
+ case wc
35
+ when Numeric
36
+ wc = [[0.0, [1.0, wc.to_f].min].max]
37
+ when Range
38
+ wc = [[0.0, [1.0, wc.begin.to_f].min].max, [0.0, [1.0, wc.end.to_f].min].max].sort
39
+ when Array
40
+ raise ArgumentError.new "Argument 3 must have exactly two elements" unless wc.length == 2
41
+ wc = [[0.0, [1.0, wc.first.to_f].min].max, [0.0, [1.0, wc.last.to_f].min].max].sort
42
+ else
43
+ raise ArgumentError.new "Argument 3 must be a numeric, a two numeric array or a range"
44
+ end
45
+ wc = wc.uniq
46
+ raise ArgumentError.new "All elemnts of W must be in the range [0, 1]" if wc.find{|v| v < 0.0 or v > 1.0}
47
+
48
+ stop = false
49
+ case type
50
+ when :low
51
+ stop = false
52
+ when :pass
53
+ stop = false
54
+ when :high
55
+ stop = true
56
+ when :stop
57
+ stop = true
58
+ else
59
+ raise ArgumentError.new "Argument 4 must be :low, :pass, :high or :stop"
60
+ end
61
+
62
+ raise ArgumentError.new "cheby1: passband ripple RP must be a non-zero scalar" unless (rp.is_a?(Float) and rp != 0.0)
63
+ rp = -20.0*Math.log10(1.0 + rp) if rp < 0.0
64
+
65
+ ## Prewarp to the band edges to s plane
66
+ t = 2.0 # sampling frequency of 2 Hz
67
+ wc.collect!{|v| 2.0 / t * Math.tan(Math::PI * v / t)}
68
+
69
+ ## Generate splane poles and zeros for the Chebyshev type 1 filter
70
+ c = 1.0 ## default cutoff frequency
71
+ epsilon = Math.sqrt(10**(rp / 10.0) - 1.0)
72
+ v0 = Math.asinh(1.0 / epsilon) / n
73
+ pole_s = (-(n-1) .. (n-1)).step(2).collect{|i| Complex.polar(1.0, Math::PI*i/(2.0*n))}
74
+ pole_s.collect!{|v| Complex(-Math.sinh(v0) * v.real, Math.cosh(v0) * v.imag)}
75
+ zero_s = []
76
+
77
+ ## compensate for amplitude at s=0
78
+ gain_s = pole_s.inject(1.0){|m, v| m * (-v)}
79
+ ## if n is even, the ripple starts low, but if n is odd the ripple
80
+ ## starts high. We must adjust the s=0 amplitude to compensate.
81
+ gain_s = gain_s / 10**(rp / 20.0) if (n & 1) == 0
82
+
83
+ ## S-plane frequency transform
84
+ zero_s, pole_s, gain_s = Roctave.sftrans(zero_s, pole_s, gain_s, wc, stop)
85
+
86
+ ## Use bilinear transform to convert poles to the Z plane
87
+ zero_z, pole_z, gain_z = Roctave.bilinear(zero_s, pole_s, gain_s, t)
88
+
89
+ ## Convert to the correct output form
90
+ b = Roctave.poly(zero_z).collect{|v| (gain_z * v).real}
91
+ a = Roctave.poly(pole_z).collect{|v| v.real}
92
+
93
+ return [b, a]
94
+ end
95
+
96
+
97
+ ##
98
+ # Generate a Chebyshev type II filter with rs dB of stopband ripple.
99
+ # @param n [Integer] The filter order
100
+ # @param rs [Float] Peak-to-peak stopband ripple. If positive, in dB, if negative, rp_dB = -20*log10(1-rs)
101
+ # @param wc [Float, Array<Float>, Range] The cutoff frequency normalized to [0 1], or the two band edges as an array or a range
102
+ # @param type [Symbol] Either :low, :high, :pass or :stop
103
+ # @return [Array<Array{Float}>] An array containing the numerator and denominator arrays of polynomial coefficients [b, a]
104
+ def self.cheby2 (n, rs, wc, type = :low)
105
+ n = [1, n.to_i].max
106
+
107
+ case wc
108
+ when Numeric
109
+ wc = [[0.0, [1.0, wc.to_f].min].max]
110
+ when Range
111
+ wc = [[0.0, [1.0, wc.begin.to_f].min].max, [0.0, [1.0, wc.end.to_f].min].max].sort
112
+ when Array
113
+ raise ArgumentError.new "Argument 3 must have exactly two elements" unless wc.length == 2
114
+ wc = [[0.0, [1.0, wc.first.to_f].min].max, [0.0, [1.0, wc.last.to_f].min].max].sort
115
+ else
116
+ raise ArgumentError.new "Argument 3 must be a numeric, a two numeric array or a range"
117
+ end
118
+ wc = wc.uniq
119
+ raise ArgumentError.new "All elemnts of W must be in the range [0, 1]" if wc.find{|v| v < 0.0 or v > 1.0}
120
+
121
+ stop = false
122
+ case type
123
+ when :low
124
+ stop = false
125
+ when :pass
126
+ stop = false
127
+ when :high
128
+ stop = true
129
+ when :stop
130
+ stop = true
131
+ else
132
+ raise ArgumentError.new "Argument 4 must be :low, :pass, :high or :stop"
133
+ end
134
+
135
+ raise ArgumentError.new "cheby2: passband ripple RS must be a non-zero scalar" unless (rs.is_a?(Float) and rs != 0.0)
136
+ rs = -20.0*Math.log10(-rs) if rs < 0.0
137
+
138
+ ## Prewarp to the band edges to s plane
139
+ t = 2.0 # sampling frequency of 2 Hz
140
+ wc.collect!{|v| 2.0 / t * Math.tan(Math::PI * v / t)}
141
+
142
+ ## Generate splane poles and zeros for the Chebyshev type 2 filter
143
+ ## From: Stearns, SD; David, RA; (1988). Signal Processing Algorithms.
144
+ ## New Jersey: Prentice-Hall.
145
+ c = 1.0 ## default cutoff frequency
146
+ lmbd = 10.0**(rs/20.0)
147
+ phi = Math.log(lmbd + Math.sqrt(lmbd**2 - 1)) / n
148
+ theta = (1..n).collect{|i| Math::PI*(i-0.5)/n}
149
+ alpha = theta.collect{|v| -Math.sinh(phi)*Math.sin(v)}
150
+ beta = theta.collect{|v| Math.cosh(phi)*Math.cos(v)}
151
+ if (n & 1) == 1 then
152
+ ## drop theta==pi/2 since it results in a zero at infinity
153
+ zero_s = ((0...(n-1)/2).to_a + ((n+3)/2-1 ... n).to_a).collect{|i| Complex(0.0, c/Math.cos(theta[i]))}
154
+ else
155
+ zero_s = theta.collect{|v| Complex(0.0, c/Math.cos(v))}
156
+ end
157
+ pole_s = (0...n).collect{|i| c/(alpha[i]**2 + beta[i]**2)*Complex(alpha[i], -beta[i])}
158
+
159
+ ## Compensate for amplitude at s=0
160
+ ## Because of the vagaries of floating point computations, the
161
+ ## prod(pole)/prod(zero) sometimes comes out as negative and
162
+ ## with a small imaginary component even though analytically
163
+ ## the gain will always be positive, hence the abs(real(...))
164
+ gain_s = (pole_s.inject(&:*) / zero_s.inject(&:*)).real.abs
165
+
166
+ ## S-plane frequency transform
167
+ zero_s, pole_s, gain_s = Roctave.sftrans(zero_s, pole_s, gain_s, wc, stop)
168
+
169
+ ## Use bilinear transform to convert poles to the Z plane
170
+ zero_z, pole_z, gain_z = Roctave.bilinear(zero_s, pole_s, gain_s, t)
171
+
172
+ ## Convert to the correct output form
173
+ b = Roctave.poly(zero_z).collect{|v| (gain_z * v).real}
174
+ a = Roctave.poly(pole_z).collect{|v| v.real}
175
+
176
+ return [b, a]
177
+ end
178
+
179
+ end
180
+