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