roctave 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,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 */
|
data/lib/roctave.rb
ADDED
@@ -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
|
+
|