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,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
+