spcore 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.rdoc +14 -1
- data/README.rdoc +10 -2
- data/lib/spcore.rb +45 -14
- data/lib/spcore/{lib → core}/circular_buffer.rb +28 -1
- data/lib/spcore/core/constants.rb +7 -1
- data/lib/spcore/{lib → core}/delay_line.rb +15 -5
- data/lib/spcore/{lib → core}/envelope_detector.rb +13 -1
- data/lib/spcore/{lib → core}/oscillator.rb +33 -3
- data/lib/spcore/core/signal.rb +350 -0
- data/lib/spcore/filters/fir/dual_sinc_filter.rb +84 -0
- data/lib/spcore/filters/fir/fir.rb +87 -0
- data/lib/spcore/filters/fir/sinc_filter.rb +68 -0
- data/lib/spcore/{lib → filters/iir}/biquad_filter.rb +7 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_allpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_bandpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_highpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_lowpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_notch_filter.rb +2 -0
- data/lib/spcore/interpolation/interpolation.rb +64 -0
- data/lib/spcore/resampling/discrete_resampling.rb +78 -0
- data/lib/spcore/resampling/hybrid_resampling.rb +30 -0
- data/lib/spcore/resampling/polynomial_resampling.rb +56 -0
- data/lib/spcore/transforms/dft.rb +47 -0
- data/lib/spcore/transforms/fft.rb +125 -0
- data/lib/spcore/{lib → util}/gain.rb +3 -0
- data/lib/spcore/{core → util}/limiters.rb +10 -1
- data/lib/spcore/util/plotter.rb +91 -0
- data/lib/spcore/{lib → util}/saturation.rb +2 -9
- data/lib/spcore/util/scale.rb +41 -0
- data/lib/spcore/util/signal_generator.rb +48 -0
- data/lib/spcore/version.rb +2 -1
- data/lib/spcore/windows/bartlett_hann_window.rb +15 -0
- data/lib/spcore/windows/bartlett_window.rb +13 -0
- data/lib/spcore/windows/blackman_harris_window.rb +14 -0
- data/lib/spcore/windows/blackman_nuttall_window.rb +14 -0
- data/lib/spcore/windows/blackman_window.rb +18 -0
- data/lib/spcore/windows/cosine_window.rb +13 -0
- data/lib/spcore/windows/flat_top_window.rb +27 -0
- data/lib/spcore/windows/gaussian_window.rb +15 -0
- data/lib/spcore/windows/hamming_window.rb +16 -0
- data/lib/spcore/windows/hann_window.rb +13 -0
- data/lib/spcore/windows/lanczos_window.rb +15 -0
- data/lib/spcore/windows/nuttall_window.rb +14 -0
- data/lib/spcore/windows/rectangular_window.rb +10 -0
- data/lib/spcore/windows/triangular_window.rb +14 -0
- data/spcore.gemspec +11 -3
- data/spec/{lib → core}/circular_buffer_spec.rb +0 -0
- data/spec/{lib → core}/delay_line_spec.rb +1 -1
- data/spec/{lib → core}/envelope_detector_spec.rb +3 -3
- data/spec/{lib → core}/oscillator_spec.rb +0 -0
- data/spec/filters/fir/dual_sinc_filter_spec.rb +64 -0
- data/spec/filters/fir/sinc_filter_spec.rb +57 -0
- data/spec/filters/iir/cookbook_filter_spec.rb +30 -0
- data/spec/interpolation/interpolation_spec.rb +49 -0
- data/spec/resampling/discrete_resampling_spec.rb +81 -0
- data/spec/resampling/hybrid_resampling_spec.rb +31 -0
- data/spec/resampling/polynomial_resampling_spec.rb +30 -0
- data/spec/transforms/dft_spec.rb +71 -0
- data/spec/transforms/fft_spec.rb +84 -0
- data/spec/{lib → util}/gain_spec.rb +2 -2
- data/spec/{core → util}/limiters_spec.rb +0 -0
- data/spec/{lib → util}/saturate_spec.rb +0 -0
- data/spec/util/signal_generator_spec.rb +54 -0
- data/spec/windows/window_spec.rb +33 -0
- metadata +90 -42
- data/lib/spcore/lib/interpolation.rb +0 -15
- data/spec/lib/cookbook_filter_spec.rb +0 -44
- data/spec/lib/interpolation_spec.rb +0 -21
@@ -0,0 +1,30 @@
|
|
1
|
+
module SPCore
|
2
|
+
# Provide resampling method using polynomial upsampling and discrete filtering
|
3
|
+
# for downsampling.
|
4
|
+
class HybridResampling
|
5
|
+
def self.resample input, sample_rate, upsample_factor, downsample_factor, filter_order
|
6
|
+
raise ArgumentError, "input.size is less than four" unless input.size >= 4
|
7
|
+
raise ArgumentError, "upsample_factor is not greater than 1" unless upsample_factor > 1
|
8
|
+
raise ArgumentError, "downsample_factor is not a Fixnum" unless downsample_factor.is_a?(Fixnum)
|
9
|
+
raise ArgumentError, "downsample_factor is not greater than 1" unless downsample_factor > 1
|
10
|
+
raise ArgumentError, "sample_rate is not greater than 0" unless sample_rate > 0
|
11
|
+
|
12
|
+
upsampled = PolynomialResampling.upsample input, sample_rate, upsample_factor
|
13
|
+
|
14
|
+
needed_samples = upsampled.size % downsample_factor
|
15
|
+
if needed_samples == 0
|
16
|
+
upsampled += Array.new(needed_samples, 0.0)
|
17
|
+
end
|
18
|
+
|
19
|
+
target_rate = sample_rate * upsample_factor / downsample_factor
|
20
|
+
cutoff = (target_rate < sample_rate) ? (target_rate / 2.0) : (sample_rate / 2.0)
|
21
|
+
|
22
|
+
filter = SincFilter.new(
|
23
|
+
:sample_rate => (sample_rate * upsample_factor), :order => filter_order,
|
24
|
+
:cutoff_freq => cutoff, :window_class => NuttallWindow
|
25
|
+
)
|
26
|
+
filtered = filter.lowpass(upsampled)
|
27
|
+
return Array.new(filtered.size / downsample_factor){ |i| filtered[i * downsample_factor] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module SPCore
|
2
|
+
# Provide resampling methods (upsampling and downsampling) using
|
3
|
+
# polynomial interpolation.
|
4
|
+
class PolynomialResampling
|
5
|
+
|
6
|
+
def self.upsample input, sample_rate, upsample_factor
|
7
|
+
raise ArgumentError, "input.size is less than four" unless input.size >= 4
|
8
|
+
raise ArgumentError, "upsample_factor is not greater than 1" unless upsample_factor > 1
|
9
|
+
raise ArgumentError, "sample_rate is not greater than 0" unless sample_rate > 0
|
10
|
+
|
11
|
+
output = Array.new((upsample_factor * input.size).to_i)
|
12
|
+
|
13
|
+
input_size_f = input.size.to_f
|
14
|
+
input_size_minus_1 = input.size - 1
|
15
|
+
input_size_minus_2 = input.size - 2
|
16
|
+
output_size_f = output.size.to_f
|
17
|
+
output.each_index do |i|
|
18
|
+
|
19
|
+
i_f = i.to_f
|
20
|
+
index_into_input = (i_f / output_size_f) * input_size_f
|
21
|
+
index_into_input_i = index_into_input.to_i
|
22
|
+
|
23
|
+
if(index_into_input <= 1.0) # before second sample
|
24
|
+
point1 = input[0]
|
25
|
+
point2 = input[0]
|
26
|
+
point3 = input[1]
|
27
|
+
point4 = input[2]
|
28
|
+
x = index_into_input
|
29
|
+
output[i] = Interpolation.cubic_hermite(point1, point2, point3, point4, x)
|
30
|
+
elsif(index_into_input >= input_size_minus_1) # past last sample
|
31
|
+
point1 = input[input_size_minus_1 - 1]
|
32
|
+
point2 = input[input_size_minus_1]
|
33
|
+
point3 = input[input_size_minus_1]
|
34
|
+
point4 = input[input_size_minus_1]
|
35
|
+
x = index_into_input - index_into_input.floor
|
36
|
+
output[i] = Interpolation.cubic_hermite(point1, point2, point3, point4, x)
|
37
|
+
elsif(index_into_input >= input_size_minus_2) # past second-to-last sample
|
38
|
+
point1 = input[index_into_input_i - 1]
|
39
|
+
point2 = input[index_into_input_i]
|
40
|
+
point3 = input[input_size_minus_1]
|
41
|
+
point4 = input[input_size_minus_1]
|
42
|
+
x = index_into_input - index_into_input.floor
|
43
|
+
output[i] = Interpolation.cubic_hermite(point1, point2, point3, point4, x)
|
44
|
+
else # general case
|
45
|
+
point1 = input[index_into_input_i - 1]
|
46
|
+
point2 = input[index_into_input_i]
|
47
|
+
point3 = input[index_into_input_i + 1]
|
48
|
+
point4 = input[index_into_input_i + 2]
|
49
|
+
x = index_into_input - index_into_input.floor
|
50
|
+
output[i] = Interpolation.cubic_hermite(point1, point2, point3, point4, x)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SPCore
|
2
|
+
# Perform DFT transforms, forward and inverse.
|
3
|
+
# @author James Tunnell
|
4
|
+
class DFT
|
5
|
+
# @param [Array] input array of real values, representing the time domain
|
6
|
+
# signal to be passed into the forward DFT.
|
7
|
+
def self.forward input
|
8
|
+
input_size = input.size
|
9
|
+
raise ArgumentError, "input.size is not even" unless (input_size % 2 == 0)
|
10
|
+
|
11
|
+
output_size = input_size
|
12
|
+
output = Array.new(output_size)
|
13
|
+
|
14
|
+
output.each_index do |k|
|
15
|
+
sum = Complex(0.0)
|
16
|
+
input.each_index do |n|
|
17
|
+
a = TWO_PI * n * k / input_size
|
18
|
+
sum += Complex(input[n] * Math::cos(a), -input[n] * Math::sin(a))
|
19
|
+
end
|
20
|
+
output[k] = sum
|
21
|
+
end
|
22
|
+
|
23
|
+
return output
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Array] input array of complex values, representing the frequency domain
|
27
|
+
# signal obtained from the forward DFT.
|
28
|
+
def self.inverse input
|
29
|
+
input_size = input.size
|
30
|
+
raise ArgumentError, "input.size is not even" unless (input_size % 2 == 0)
|
31
|
+
|
32
|
+
output = Array.new(input_size)
|
33
|
+
output_size = output.size
|
34
|
+
|
35
|
+
output.each_index do |k|
|
36
|
+
sum = Complex(0.0)
|
37
|
+
input.each_index do |n|
|
38
|
+
a = TWO_PI * n * k / input_size
|
39
|
+
sum += Complex(input[n] * Math::cos(a), input[n] * Math::sin(a))
|
40
|
+
end
|
41
|
+
output[k] = sum / output_size
|
42
|
+
end
|
43
|
+
|
44
|
+
return output
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module SPCore
|
2
|
+
# Perform FFT transforms, forward and inverse.
|
3
|
+
# @author James Tunnell
|
4
|
+
class FFT
|
5
|
+
|
6
|
+
# Forward Radix-2 FFT transform using decimation-in-time. Operates on an array
|
7
|
+
# of real values, representing a time domain signal.
|
8
|
+
# Ported from unlicensed MATLAB code which was posted to the MathWorks file
|
9
|
+
# exchange by Dinesh Dileep Gaurav.
|
10
|
+
# See http://www.mathworks.com/matlabcentral/fileexchange/17778.
|
11
|
+
# @param [Array] input An array of numeric values. If size is not an exact
|
12
|
+
# radix-2 number, then zeros will be appended until it is,
|
13
|
+
# unless force_radix_2_size is set false.
|
14
|
+
# @param force_radix_2_size If true, any input that does not have an exact
|
15
|
+
# power-of-two size will be appended with zeros
|
16
|
+
# until it does. Set true by default.
|
17
|
+
# @raise [ArgumentError] if input size is not an exact radix-2 number and
|
18
|
+
# force_radix_2_size is false.
|
19
|
+
def self.forward input, force_radix_2_size = true
|
20
|
+
size = input.size
|
21
|
+
power_of_two = Math::log2(size)
|
22
|
+
if power_of_two.floor != power_of_two # input size is not an even power of two
|
23
|
+
if force_radix_2_size
|
24
|
+
new_size = 2**(power_of_two.to_i() + 1)
|
25
|
+
input += Array.new(new_size - size, 0.0)
|
26
|
+
|
27
|
+
size = input.size
|
28
|
+
power_of_two = Math::log2(size)
|
29
|
+
else
|
30
|
+
raise ArgumentError, "input.size #{size} is not a radix-2 number"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
power_of_two = power_of_two.to_i
|
34
|
+
x = bit_reverse_order input, power_of_two
|
35
|
+
|
36
|
+
phase = Array.new(size/2){|n|
|
37
|
+
Complex(Math::cos(TWO_PI*n/size), -Math::sin(TWO_PI*n/size))
|
38
|
+
}
|
39
|
+
for a in 1..power_of_two
|
40
|
+
l = 2**a
|
41
|
+
phase_level = []
|
42
|
+
|
43
|
+
0.step(size/2, size/l) do |i|
|
44
|
+
phase_level << phase[i]
|
45
|
+
end
|
46
|
+
|
47
|
+
ks = []
|
48
|
+
0.step(size-l, l) do |k|
|
49
|
+
ks << k
|
50
|
+
end
|
51
|
+
|
52
|
+
ks.each do |k|
|
53
|
+
for n in 0...l/2
|
54
|
+
idx1 = n+k
|
55
|
+
idx2 = n+k + (l/2)
|
56
|
+
|
57
|
+
first = x[idx1]
|
58
|
+
second = x[idx2] * phase_level[n]
|
59
|
+
x[idx1] = first + second
|
60
|
+
x[idx2] = first - second
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return x
|
65
|
+
end
|
66
|
+
|
67
|
+
# Inverse Radix-2 FFT transform. Operates on an array of complex values, as
|
68
|
+
# one would obtain from the forward FFT transform.
|
69
|
+
# Ported from unlicensed MATLAB code which was posted to the MathWorks file
|
70
|
+
# exchange by Dinesh Dileep Gaurav.
|
71
|
+
# See http://www.mathworks.com/matlabcentral/fileexchange/17778.
|
72
|
+
# @param [Array] input An array of complex values. Must have a radix-2 size
|
73
|
+
# (2, 4, 8, 16, 32, ...).
|
74
|
+
# @raise [ArgumentError] if input size is not an exact power-of-two.
|
75
|
+
def self.inverse input
|
76
|
+
size = input.size
|
77
|
+
power_of_two = Math::log2(size)
|
78
|
+
raise ArgumentError, "input.size #{size} is not power of 2" if power_of_two.floor != power_of_two
|
79
|
+
power_of_two = power_of_two.to_i
|
80
|
+
x = bit_reverse_order input, power_of_two
|
81
|
+
|
82
|
+
phase = Array.new(size/2){|n|
|
83
|
+
Complex(Math::cos(TWO_PI*n/size), -Math::sin(TWO_PI*n/size))
|
84
|
+
}
|
85
|
+
for a in 1..power_of_two
|
86
|
+
l = 2**a
|
87
|
+
phase_level = []
|
88
|
+
|
89
|
+
0.step(size/2, size/l) do |i|
|
90
|
+
phase_level << phase[i]
|
91
|
+
end
|
92
|
+
|
93
|
+
ks = []
|
94
|
+
0.step(size-l, l) do |k|
|
95
|
+
ks << k
|
96
|
+
end
|
97
|
+
|
98
|
+
ks.each do |k|
|
99
|
+
for n in 0...l/2
|
100
|
+
idx1 = n+k
|
101
|
+
idx2 = n+k + (l/2)
|
102
|
+
|
103
|
+
first = x[idx1]
|
104
|
+
second = x[idx2] * phase_level[n]
|
105
|
+
x[idx1] = (first + second)
|
106
|
+
x[idx2] = (first - second)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
return x.map {|val| val / size }
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def self.get_bit_reversed_addr i, nbits
|
117
|
+
i.to_s(2).rjust(nbits, '0').reverse!.to_i(2)
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.bit_reverse_order input, m
|
121
|
+
Array.new(input.size){|i| input[get_bit_reversed_addr(i, m)] }
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module SPCore
|
2
|
+
# Provide utility functions to convert between a linear and decibel (logarithm) unit.
|
2
3
|
class Gain
|
3
4
|
|
4
5
|
#MAX_DB = 72
|
@@ -167,12 +168,14 @@ class Gain
|
|
167
168
|
|
168
169
|
MAX_DB_ABS = 6000.0
|
169
170
|
|
171
|
+
# Convert a decibel value to a linear value.
|
170
172
|
def self.db_to_linear db
|
171
173
|
db_abs = db.abs
|
172
174
|
raise ArgumentError, "|db| is #{db_abs}, which is greater than the allowed #{MAX_DB_ABS}" if db_abs > MAX_DB_ABS
|
173
175
|
return 10.0**(db / 20.0)
|
174
176
|
end
|
175
177
|
|
178
|
+
# Convert a linear value to a decibel value.
|
176
179
|
def self.linear_to_db linear
|
177
180
|
raise ArgumentError, "linear value #{linear} is less than or equal to 0.0" if linear <= 0.0
|
178
181
|
return 20.0 * Math::log10(linear)
|
@@ -1,11 +1,15 @@
|
|
1
1
|
module SPCore
|
2
|
+
# Provides methods to make limiting Proc objects, bound to
|
3
|
+
# the given limits.
|
2
4
|
class Limiters
|
5
|
+
# make a limiter that actually doesn't limit at all.
|
3
6
|
def self.make_no_limiter
|
4
7
|
return lambda do |input|
|
5
8
|
return input
|
6
9
|
end
|
7
10
|
end
|
8
|
-
|
11
|
+
|
12
|
+
# make a limiter that keeps values within the given range.
|
9
13
|
def self.make_range_limiter range
|
10
14
|
return lambda do |input|
|
11
15
|
if input < range.first
|
@@ -18,6 +22,7 @@ class Limiters
|
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
25
|
+
# make a limiter that keeps values at or below the given upper limit.
|
21
26
|
def self.make_upper_limiter limit
|
22
27
|
return lambda do |input|
|
23
28
|
if input > limit
|
@@ -28,6 +33,7 @@ class Limiters
|
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
36
|
+
# make a limiter that keeps values at or above the given lower limit.
|
31
37
|
def self.make_lower_limiter limit
|
32
38
|
return lambda do |input|
|
33
39
|
if input < limit
|
@@ -38,6 +44,9 @@ class Limiters
|
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
47
|
+
# make a limiter that limits values to a set of good values. Given also the
|
48
|
+
# current value, it either returns the input value if it's included in
|
49
|
+
# the set of good values, or it returns the current value.
|
41
50
|
def self.make_enum_limiter good_values
|
42
51
|
return lambda do |input, current|
|
43
52
|
if good_values.include?(input)
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'gnuplot'
|
2
|
+
|
3
|
+
module SPCore
|
4
|
+
|
5
|
+
# Helps make plotting data even easier. Uses gnuplot.
|
6
|
+
class Plotter
|
7
|
+
include Hashmake::HashMakeable
|
8
|
+
|
9
|
+
# Used to process hashed args passed to #initialize.
|
10
|
+
ARG_SPECS = [
|
11
|
+
Hashmake::ArgSpec.new(:key => :title, :type => String, :reqd => false, :default => ""),
|
12
|
+
Hashmake::ArgSpec.new(:key => :xlabel, :type => String, :reqd => false, :default => "x"),
|
13
|
+
Hashmake::ArgSpec.new(:key => :ylabel, :type => String, :reqd => false, :default => "y"),
|
14
|
+
Hashmake::ArgSpec.new(:key => :linestyle, :type => String, :reqd => false, :default => "lines"),
|
15
|
+
Hashmake::ArgSpec.new(:key => :linewidth, :type => Fixnum, :reqd => false, :default => 1, :validator => ->(a){ a >= 1 }),
|
16
|
+
Hashmake::ArgSpec.new(:key => :logscale, :type => String, :reqd => false, :default => ""),
|
17
|
+
]
|
18
|
+
|
19
|
+
# A new instance of Plotter.
|
20
|
+
# @param [Hash] hashed_args A hash containing initialization parameters.
|
21
|
+
# All params are optional. See ARG_SPECS for
|
22
|
+
# parameter details.
|
23
|
+
def initialize hashed_args = {}
|
24
|
+
hash_make Plotter::ARG_SPECS, hashed_args
|
25
|
+
end
|
26
|
+
|
27
|
+
# Plot XY datapoints.
|
28
|
+
# @param [Hash] titled_hashes A hash that maps dataset titles to data. The data itself
|
29
|
+
# is a hash also, that maps x values to y values. For example,
|
30
|
+
# plot_2d could be called passing it the hash { "somedata" => {0.0 => 4.0, 1.0 => 2.0}}
|
31
|
+
def plot_2d titled_hashes
|
32
|
+
datasets = []
|
33
|
+
titled_hashes.each do |title, hash|
|
34
|
+
dataset = Gnuplot::DataSet.new( [hash.keys, hash.values] ){ |ds|
|
35
|
+
ds.with = @linestyle
|
36
|
+
ds.title = title
|
37
|
+
ds.linewidth = @linewidth
|
38
|
+
}
|
39
|
+
datasets << dataset
|
40
|
+
end
|
41
|
+
|
42
|
+
plot_datasets datasets
|
43
|
+
end
|
44
|
+
|
45
|
+
# Plot a sequence of values.
|
46
|
+
# @param [Hash] titled_sequences A hash that maps sequence titles to data. The data itself
|
47
|
+
# is an array of values. In the plot, values will be mapped to
|
48
|
+
# their index in the sequence. For example, plot_1d could be
|
49
|
+
# called passing it the hash { "somedataseq" => [0,2,3,6,3,-1]}
|
50
|
+
# @param plot_against_fraction If true, instead of plotting samples against sample number, plot
|
51
|
+
# them against the fraction (sample_number / total_samples).
|
52
|
+
def plot_1d titled_sequences, plot_against_fraction = false
|
53
|
+
datasets = []
|
54
|
+
titled_sequences.each do |title, sequence|
|
55
|
+
indices = Array.new(sequence.size)
|
56
|
+
sequence.each_index do |i|
|
57
|
+
indices[i] = i
|
58
|
+
if plot_against_fraction
|
59
|
+
indices[i] /= sequence.size.to_f
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
dataset = Gnuplot::DataSet.new( [indices, sequence] ){ |ds|
|
64
|
+
ds.with = @linestyle
|
65
|
+
ds.title = title
|
66
|
+
ds.linewidth = @linewidth
|
67
|
+
}
|
68
|
+
datasets << dataset
|
69
|
+
end
|
70
|
+
|
71
|
+
plot_datasets datasets
|
72
|
+
end
|
73
|
+
|
74
|
+
# Plot Gnuplot::DataSet objects.
|
75
|
+
# @param [Array] datasets An array of Gnuplot::DataSet objects.
|
76
|
+
def plot_datasets datasets
|
77
|
+
Gnuplot.open do |gp|
|
78
|
+
Gnuplot::Plot.new(gp) do |plot|
|
79
|
+
plot.title @title
|
80
|
+
plot.xlabel @xlabel
|
81
|
+
plot.ylabel @ylabel
|
82
|
+
plot.data = datasets
|
83
|
+
|
84
|
+
if @logscale_x
|
85
|
+
plot.logscale "x"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module SPCore
|
2
|
+
# Provide simple saturation methods, that limit input above the given threshold value.
|
2
3
|
class Saturation
|
3
4
|
# Sigmoid-based saturation when input is above threshold.
|
4
5
|
# From musicdsp.org, posted by Bram.
|
@@ -18,6 +19,7 @@ class Saturation
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
22
|
+
# A Gompertz-sigmoid-based saturation when input is above threshold.
|
21
23
|
def self.gompertz input, threshold
|
22
24
|
a = threshold
|
23
25
|
b = -4
|
@@ -32,14 +34,5 @@ class Saturation
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
35
|
-
private
|
36
|
-
|
37
|
-
#def self.mock_sigmoid x
|
38
|
-
# if(x.abs < 1.0)
|
39
|
-
# return x * (1.5 - 0.5 * x * x)
|
40
|
-
# else
|
41
|
-
# return x > 0.0 ? 1.0 : -1.0
|
42
|
-
# end
|
43
|
-
#end
|
44
37
|
end
|
45
38
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module SPCore
|
2
|
+
# Provide methods for generating sequences that scale linearly or exponentially.
|
3
|
+
class Scale
|
4
|
+
# Produce a sequence of values that progresses in a linear fashion.
|
5
|
+
# @param [Range] range The start and end values the set should include.
|
6
|
+
# @param [Fixnum] n_points The number of points to create for the sequence, including the start and end points.
|
7
|
+
# @raise [ArgumentError] if n_points is < 2.
|
8
|
+
def self.linear range, n_points
|
9
|
+
raise ArgumentError, "n_points is < 2" if n_points < 2
|
10
|
+
incr = (range.last - range.first) / (n_points - 1)
|
11
|
+
points = Array.new(n_points)
|
12
|
+
value = range.first
|
13
|
+
|
14
|
+
points.each_index do |i|
|
15
|
+
points[i] = value
|
16
|
+
value += incr
|
17
|
+
end
|
18
|
+
|
19
|
+
return points
|
20
|
+
end
|
21
|
+
|
22
|
+
# Produce a sequence of values that progresses in an exponential fashion.
|
23
|
+
|
24
|
+
# @param [Range] range The start and end values the set should include.
|
25
|
+
# @param [Fixnum] n_points The number of points to create for the sequence, including the start and end points.
|
26
|
+
# @raise [ArgumentError] if n_points is < 2.
|
27
|
+
def self.exponential range, n_points
|
28
|
+
raise ArgumentError, "n_points is < 2" if n_points < 2
|
29
|
+
multiplier = (range.last / range.first)**(1.0/(n_points-1))
|
30
|
+
points = Array.new(n_points)
|
31
|
+
value = range.first
|
32
|
+
|
33
|
+
points.each_index do |i|
|
34
|
+
points[i] = value
|
35
|
+
value *= multiplier
|
36
|
+
end
|
37
|
+
|
38
|
+
return points
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|