spcore 0.1.2 → 0.1.3
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.
- 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
|