spcore 0.1.8 → 0.1.9
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 +6 -1
- data/README.rdoc +1 -1
- data/lib/spcore.rb +5 -3
- data/lib/spcore/{core → analysis}/envelope.rb +1 -1
- data/lib/spcore/{core → analysis}/extrema.rb +0 -0
- data/lib/spcore/{core → analysis}/signal.rb +178 -95
- data/lib/spcore/transforms/fft.rb +5 -0
- data/lib/spcore/util/plotter.rb +8 -0
- data/lib/spcore/version.rb +1 -1
- data/lib/spcore/windows/tukey_window.rb +32 -0
- data/spec/analysis/envelope_spec.rb +43 -0
- data/spec/{core → analysis}/extrema_spec.rb +0 -0
- data/spec/analysis/signal_spec.rb +68 -0
- data/spec/windows/window_spec.rb +16 -15
- metadata +14 -11
- data/spec/core/envelope_spec.rb +0 -29
data/ChangeLog.rdoc
CHANGED
@@ -43,4 +43,9 @@ Require all sample rate args to be Fixnum.
|
|
43
43
|
|
44
44
|
=== 0.1.8 / 2013-05-03
|
45
45
|
|
46
|
-
Add Envelope and Extrema classes, for signal analysis.
|
46
|
+
Add Envelope and Extrema classes, for signal analysis.
|
47
|
+
|
48
|
+
=== 0.1.9 / 2013-05-06
|
49
|
+
|
50
|
+
Add TukeyWindow.
|
51
|
+
Add Signal#keep_frequences, Signal#remove_frequencies, and Plotter#plot_signals.
|
data/README.rdoc
CHANGED
@@ -13,7 +13,7 @@ A library of signal processing methods and classes.
|
|
13
13
|
* Resampling (discrete up, down and up/down, polynomial up, and hybrid up/down)
|
14
14
|
* FFT transform (forward and inverse)
|
15
15
|
* DFT transform (forward and inverse)
|
16
|
-
* Windows (Blackman, Hamming,
|
16
|
+
* Windows (Blackman, Hamming, and many more...)
|
17
17
|
* Windowed sinc filter for lowpass and highpass.
|
18
18
|
* Dual windowed sinc filter for bandpass and bandstop.
|
19
19
|
* Interpolation (linear and polynomial)
|
data/lib/spcore.rb
CHANGED
@@ -5,9 +5,6 @@ require 'spcore/core/circular_buffer'
|
|
5
5
|
require 'spcore/core/constants'
|
6
6
|
require 'spcore/core/delay_line'
|
7
7
|
require 'spcore/core/oscillator'
|
8
|
-
require 'spcore/core/signal'
|
9
|
-
require 'spcore/core/extrema'
|
10
|
-
require 'spcore/core/envelope'
|
11
8
|
|
12
9
|
require 'spcore/windows/bartlett_hann_window'
|
13
10
|
require 'spcore/windows/bartlett_window'
|
@@ -23,6 +20,7 @@ require 'spcore/windows/lanczos_window'
|
|
23
20
|
require 'spcore/windows/nuttall_window'
|
24
21
|
require 'spcore/windows/rectangular_window'
|
25
22
|
require 'spcore/windows/triangular_window'
|
23
|
+
require 'spcore/windows/tukey_window'
|
26
24
|
|
27
25
|
require 'spcore/filters/fir/fir'
|
28
26
|
require 'spcore/filters/fir/sinc_filter'
|
@@ -43,6 +41,10 @@ require 'spcore/resampling/discrete_resampling'
|
|
43
41
|
require 'spcore/resampling/polynomial_resampling'
|
44
42
|
require 'spcore/resampling/hybrid_resampling'
|
45
43
|
|
44
|
+
require 'spcore/analysis/extrema'
|
45
|
+
require 'spcore/analysis/envelope'
|
46
|
+
require 'spcore/analysis/signal'
|
47
|
+
|
46
48
|
require 'spcore/util/gain'
|
47
49
|
require 'spcore/util/limiters'
|
48
50
|
require 'spcore/util/plotter'
|
File without changes
|
@@ -54,57 +54,98 @@ class Signal
|
|
54
54
|
end
|
55
55
|
|
56
56
|
# Increase the sample rate of signal data by the given factor using
|
57
|
-
# discrete upsampling method.
|
57
|
+
# discrete upsampling method. Modifies current object.
|
58
58
|
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
59
59
|
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
60
|
-
def upsample_discrete upsample_factor, filter_order
|
60
|
+
def upsample_discrete! upsample_factor, filter_order
|
61
61
|
@data = DiscreteResampling.upsample @data, @sample_rate, upsample_factor, filter_order
|
62
62
|
@sample_rate *= upsample_factor
|
63
63
|
return self
|
64
64
|
end
|
65
65
|
|
66
|
+
# Increase the sample rate of signal data by the given factor using
|
67
|
+
# discrete upsampling method. Return result in a new Signal object.
|
68
|
+
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
69
|
+
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
70
|
+
def upsample_discrete upsample_factor, filter_order
|
71
|
+
return self.clone.upsample_discrete!(upsample_factor, filter_order)
|
72
|
+
end
|
73
|
+
|
66
74
|
# Decrease the sample rate of signal data by the given factor using
|
67
|
-
# discrete downsampling method.
|
75
|
+
# discrete downsampling method. Modifies current object.
|
68
76
|
# @param [Fixnum] downsample_factor Decrease the sample rate by this factor.
|
69
77
|
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
70
|
-
def downsample_discrete downsample_factor, filter_order
|
78
|
+
def downsample_discrete! downsample_factor, filter_order
|
71
79
|
@data = DiscreteResampling.downsample @data, @sample_rate, downsample_factor, filter_order
|
72
80
|
@sample_rate /= downsample_factor
|
73
81
|
return self
|
74
82
|
end
|
75
83
|
|
84
|
+
# Decrease the sample rate of signal data by the given factor using
|
85
|
+
# discrete downsampling method. Return result in a new Signal object.
|
86
|
+
# @param [Fixnum] downsample_factor Decrease the sample rate by this factor.
|
87
|
+
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
88
|
+
def downsample_discrete downsample_factor, filter_order
|
89
|
+
return self.clone.downsample_discrete!(downsample_factor, filter_order)
|
90
|
+
end
|
91
|
+
|
76
92
|
# Change the sample rate of signal data by the given up/down factors, using
|
77
|
-
# discrete upsampling and downsampling methods.
|
93
|
+
# discrete upsampling and downsampling methods. Modifies current object.
|
78
94
|
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
79
95
|
# @param [Fixnum] downsample_factor Decrease the sample rate by this factor.
|
80
96
|
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
81
|
-
def resample_discrete upsample_factor, downsample_factor, filter_order
|
97
|
+
def resample_discrete! upsample_factor, downsample_factor, filter_order
|
82
98
|
@data = DiscreteResampling.resample @data, @sample_rate, upsample_factor, downsample_factor, filter_order
|
83
99
|
@sample_rate *= upsample_factor
|
84
100
|
@sample_rate /= downsample_factor
|
85
101
|
return self
|
86
102
|
end
|
103
|
+
|
104
|
+
# Change the sample rate of signal data by the given up/down factors, using
|
105
|
+
# discrete upsampling and downsampling methods. Return result in a new Signal object.
|
106
|
+
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
107
|
+
# @param [Fixnum] downsample_factor Decrease the sample rate by this factor.
|
108
|
+
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
109
|
+
def resample_discrete upsample_factor, downsample_factor, filter_order
|
110
|
+
return self.clone.resample_discrete!(upsample_factor, downsample_factor, filter_order)
|
111
|
+
end
|
87
112
|
|
88
113
|
# Increase the sample rate of signal data by the given factor using
|
89
|
-
# polynomial interpolation.
|
114
|
+
# polynomial interpolation. Modifies current Signal object.
|
90
115
|
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
91
|
-
def upsample_polynomial upsample_factor
|
116
|
+
def upsample_polynomial! upsample_factor
|
92
117
|
@data = PolynomialResampling.upsample @data, upsample_factor
|
93
118
|
@sample_rate *= upsample_factor
|
94
119
|
return self
|
95
120
|
end
|
96
121
|
|
122
|
+
# Increase the sample rate of signal data by the given factor using
|
123
|
+
# polynomial interpolation. Returns result as a new Signal object.
|
124
|
+
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
125
|
+
def upsample_polynomial upsample_factor
|
126
|
+
return self.clone.upsample_polynomial!(upsample_factor)
|
127
|
+
end
|
128
|
+
|
97
129
|
# Change the sample rate of signal data by the given up/down factors, using
|
98
|
-
# polynomial upsampling and discrete downsampling.
|
130
|
+
# polynomial upsampling and discrete downsampling. Modifies current Signal object.
|
99
131
|
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
100
132
|
# @param [Fixnum] downsample_factor Decrease the sample rate by this factor.
|
101
133
|
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
102
|
-
def resample_hybrid upsample_factor, downsample_factor, filter_order
|
134
|
+
def resample_hybrid! upsample_factor, downsample_factor, filter_order
|
103
135
|
@data = HybridResampling.resample @data, @sample_rate, upsample_factor, downsample_factor, filter_order
|
104
136
|
@sample_rate *= upsample_factor
|
105
137
|
@sample_rate /= downsample_factor
|
106
138
|
return self
|
107
139
|
end
|
140
|
+
|
141
|
+
# Change the sample rate of signal data by the given up/down factors, using
|
142
|
+
# polynomial upsampling and discrete downsampling. Return result as a new Signal object.
|
143
|
+
# @param [Fixnum] upsample_factor Increase the sample rate by this factor.
|
144
|
+
# @param [Fixnum] downsample_factor Decrease the sample rate by this factor.
|
145
|
+
# @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
|
146
|
+
def resample_hybrid upsample_factor, downsample_factor, filter_order
|
147
|
+
return self.clone.resample_hybrid!(upsample_factor, downsample_factor, filter_order)
|
148
|
+
end
|
108
149
|
|
109
150
|
# Run FFT on signal data to find magnitude of frequency components.
|
110
151
|
# @param convert_to_db If true, magnitudes are converted to dB values.
|
@@ -134,10 +175,27 @@ class Signal
|
|
134
175
|
return @data.inject(0.0){|sum,x| sum + (x * x)}
|
135
176
|
end
|
136
177
|
|
137
|
-
#
|
138
|
-
#
|
139
|
-
def
|
140
|
-
|
178
|
+
# Calculate signal RMS (root-mean square), also known as quadratic mean, a
|
179
|
+
# statistical measure of the magnitude.
|
180
|
+
def rms
|
181
|
+
Math.sqrt(energy / size)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Find extrema (maxima, minima) within signal data.
|
185
|
+
def extrema
|
186
|
+
return Extrema.new(@data)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Determine the envelope of the current Signal and return either a Envelope
|
190
|
+
# or a new Signal object as a result.
|
191
|
+
# @param [True/False] make_signal If true, return envelope data in a new
|
192
|
+
# Otherwise, return an Envelope object.
|
193
|
+
def envelope make_signal = false
|
194
|
+
if make_signal
|
195
|
+
return Signal.new(:sample_rate => @sample_rate, :data => Envelope.new(@data).data)
|
196
|
+
else
|
197
|
+
return Envelope.new(@data)
|
198
|
+
end
|
141
199
|
end
|
142
200
|
|
143
201
|
# Add data in array or other signal to the beginning of current data.
|
@@ -150,6 +208,11 @@ class Signal
|
|
150
208
|
return self
|
151
209
|
end
|
152
210
|
|
211
|
+
# Add data in array or other signal to the beginning of current data.
|
212
|
+
def prepend other
|
213
|
+
clone.prepend! other
|
214
|
+
end
|
215
|
+
|
153
216
|
# Add data in array or other signal to the end of current data.
|
154
217
|
def append! other
|
155
218
|
if other.is_a?(Array)
|
@@ -159,6 +222,11 @@ class Signal
|
|
159
222
|
end
|
160
223
|
return self
|
161
224
|
end
|
225
|
+
|
226
|
+
# Add data in array or other signal to the end of current data.
|
227
|
+
def append other
|
228
|
+
clone.append! other
|
229
|
+
end
|
162
230
|
|
163
231
|
# Add value, values in array, or values in other signal to the current
|
164
232
|
# data values, and update the current data with the results.
|
@@ -188,26 +256,7 @@ class Signal
|
|
188
256
|
# data values, and return a new Signal object with the results.
|
189
257
|
# @param other Can be Numeric (add same value to all data values), Array, or Signal.
|
190
258
|
def add(other)
|
191
|
-
|
192
|
-
|
193
|
-
if other.is_a?(Numeric)
|
194
|
-
@data.each_index do |i|
|
195
|
-
new_data[i] = @data[i] + other
|
196
|
-
end
|
197
|
-
elsif other.is_a?(Signal)
|
198
|
-
raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size
|
199
|
-
@data.each_index do |i|
|
200
|
-
new_data[i] = @data[i] + other.data[i]
|
201
|
-
end
|
202
|
-
elsif other.is_a?(Array)
|
203
|
-
raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size
|
204
|
-
@data.each_index do |i|
|
205
|
-
new_data[i] = @data[i] + other[i]
|
206
|
-
end
|
207
|
-
else
|
208
|
-
raise ArgumentError, "other is not a Numeric, Signal, or Array"
|
209
|
-
end
|
210
|
-
return Signal.new(:data => new_data, :sample_rate => @sample_rate)
|
259
|
+
clone.add! other
|
211
260
|
end
|
212
261
|
|
213
262
|
# Subtract value, values in array, or values in other signal from the current
|
@@ -238,26 +287,7 @@ class Signal
|
|
238
287
|
# data values, and return a new Signal object with the results.
|
239
288
|
# @param other Can be Numeric (subtract same value from all data values), Array, or Signal.
|
240
289
|
def subtract(other)
|
241
|
-
|
242
|
-
|
243
|
-
if other.is_a?(Numeric)
|
244
|
-
@data.each_index do |i|
|
245
|
-
new_data[i] = @data[i] - other
|
246
|
-
end
|
247
|
-
elsif other.is_a?(Signal)
|
248
|
-
raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size
|
249
|
-
@data.each_index do |i|
|
250
|
-
new_data[i] = @data[i] - other.data[i]
|
251
|
-
end
|
252
|
-
elsif other.is_a?(Array)
|
253
|
-
raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size
|
254
|
-
@data.each_index do |i|
|
255
|
-
new_data[i] = @data[i] - other[i]
|
256
|
-
end
|
257
|
-
else
|
258
|
-
raise ArgumentError, "other is not a Numeric, Signal, or Array"
|
259
|
-
end
|
260
|
-
return Signal.new(:data => new_data, :sample_rate => @sample_rate)
|
290
|
+
clone.subtract! other
|
261
291
|
end
|
262
292
|
|
263
293
|
# Multiply value, values in array, or values in other signal with the current
|
@@ -290,26 +320,7 @@ class Signal
|
|
290
320
|
# @param other Can be Numeric (multiply all data values by the same value),
|
291
321
|
# Array, or Signal.
|
292
322
|
def multiply(other)
|
293
|
-
|
294
|
-
|
295
|
-
if other.is_a?(Numeric)
|
296
|
-
@data.each_index do |i|
|
297
|
-
new_data[i] = @data[i] * other
|
298
|
-
end
|
299
|
-
elsif other.is_a?(Signal)
|
300
|
-
raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size
|
301
|
-
@data.each_index do |i|
|
302
|
-
new_data[i] = @data[i] * other.data[i]
|
303
|
-
end
|
304
|
-
elsif other.is_a?(Array)
|
305
|
-
raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size
|
306
|
-
@data.each_index do |i|
|
307
|
-
new_data[i] = @data[i] * other[i]
|
308
|
-
end
|
309
|
-
else
|
310
|
-
raise ArgumentError, "other is not a Numeric, Signal, or Array"
|
311
|
-
end
|
312
|
-
return Signal.new(:data => new_data, :sample_rate => @sample_rate)
|
323
|
+
clone.multiply! other
|
313
324
|
end
|
314
325
|
|
315
326
|
# Divide value, values in array, or values in other signal into the current
|
@@ -342,26 +353,7 @@ class Signal
|
|
342
353
|
# @param other Can be Numeric (divide same all data values by the same value),
|
343
354
|
# Array, or Signal.
|
344
355
|
def divide(other)
|
345
|
-
|
346
|
-
|
347
|
-
if other.is_a?(Numeric)
|
348
|
-
@data.each_index do |i|
|
349
|
-
new_data[i] = @data[i] / other
|
350
|
-
end
|
351
|
-
elsif other.is_a?(Signal)
|
352
|
-
raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size
|
353
|
-
@data.each_index do |i|
|
354
|
-
new_data[i] = @data[i] / other.data[i]
|
355
|
-
end
|
356
|
-
elsif other.is_a?(Array)
|
357
|
-
raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size
|
358
|
-
@data.each_index do |i|
|
359
|
-
new_data[i] = @data[i] / other[i]
|
360
|
-
end
|
361
|
-
else
|
362
|
-
raise ArgumentError, "other is not a Numeric, Signal, or Array"
|
363
|
-
end
|
364
|
-
return Signal.new(:data => new_data, :sample_rate => @sample_rate)
|
356
|
+
clone.divide! other
|
365
357
|
end
|
366
358
|
|
367
359
|
alias_method :+, :add
|
@@ -446,6 +438,97 @@ class Signal
|
|
446
438
|
|
447
439
|
return cross_correlation
|
448
440
|
end
|
449
|
-
end
|
450
441
|
|
442
|
+
# Removes all but the given range of frequencies from the signal, using
|
443
|
+
# frequency domain filtering. Modifes and returns the current object.
|
444
|
+
def remove_frequencies! freq_range
|
445
|
+
modify_freq_content freq_range, :remove
|
446
|
+
end
|
447
|
+
|
448
|
+
# Removes the given range of frequencies from the signal, using
|
449
|
+
# frequency domain filtering. Modifes a clone of the current object,
|
450
|
+
# returning the clone.
|
451
|
+
def remove_frequencies freq_range
|
452
|
+
return self.clone.remove_frequencies!(freq_range)
|
453
|
+
end
|
454
|
+
|
455
|
+
# Removes all but the given range of frequencies from the signal, using
|
456
|
+
# frequency domain filtering. Modifes and returns the current object.
|
457
|
+
def keep_frequencies! freq_range
|
458
|
+
modify_freq_content freq_range, :keep
|
459
|
+
end
|
460
|
+
|
461
|
+
# Removes all but the given range of frequencies from the signal, using
|
462
|
+
# frequency domain filtering. Modifes a clone of the current object,
|
463
|
+
# returning the clone.
|
464
|
+
def keep_frequencies freq_range
|
465
|
+
return self.clone.keep_frequencies!(freq_range)
|
466
|
+
end
|
467
|
+
|
468
|
+
private
|
469
|
+
|
470
|
+
def modify_freq_content freq_range, mod_type
|
471
|
+
nyquist = @sample_rate / 2
|
472
|
+
|
473
|
+
unless freq_range.min.between?(0, nyquist)
|
474
|
+
raise ArgumentError, "freq_range.min #{freq_range.min} is not between 0 and #{nyquist}"
|
475
|
+
end
|
476
|
+
|
477
|
+
unless freq_range.max.between?(0, nyquist)
|
478
|
+
raise ArgumentError, "freq_range.min #{freq_range.min} is not between 0 and #{nyquist}"
|
479
|
+
end
|
480
|
+
|
481
|
+
power_of_two = FFT.power_of_two?(size)
|
482
|
+
if power_of_two
|
483
|
+
freq_domain = FFT.forward @data
|
484
|
+
else
|
485
|
+
freq_domain = DFT.forward @data
|
486
|
+
end
|
487
|
+
|
488
|
+
# cutoff indices for real half
|
489
|
+
a = ((freq_range.min * size) / @sample_rate).round
|
490
|
+
b = ((freq_range.max * size) / @sample_rate).round
|
491
|
+
|
492
|
+
window_size = b - a + 1
|
493
|
+
window_data = RectangularWindow.new(window_size).data
|
494
|
+
|
495
|
+
case mod_type
|
496
|
+
when :keep
|
497
|
+
new_freq_data = Array.new(size, Complex(0))
|
498
|
+
|
499
|
+
window_size.times do |n|
|
500
|
+
i = n + a
|
501
|
+
new_freq_data[i] = freq_domain[i] * window_data[n]
|
502
|
+
end
|
503
|
+
|
504
|
+
window_size.times do |n|
|
505
|
+
i = n + (size - 1 - b)
|
506
|
+
new_freq_data[i] = freq_domain[i] * window_data[n]
|
507
|
+
end
|
508
|
+
when :remove
|
509
|
+
new_freq_data = freq_domain.clone
|
510
|
+
|
511
|
+
window_size.times do |n|
|
512
|
+
i = n + a
|
513
|
+
new_freq_data[i] = freq_domain[i] * (Complex(1.0) - window_data[n])
|
514
|
+
end
|
515
|
+
|
516
|
+
window_size.times do |n|
|
517
|
+
i = n + (size - 1 - b)
|
518
|
+
new_freq_data[i] = freq_domain[i] * (Complex(1.0) - window_data[n])
|
519
|
+
end
|
520
|
+
else
|
521
|
+
raise ArgumentError, "unkown mod_type #{mod_type}"
|
522
|
+
end
|
523
|
+
|
524
|
+
if power_of_two
|
525
|
+
data = FFT.inverse new_freq_data
|
526
|
+
else
|
527
|
+
data = DFT.inverse new_freq_data
|
528
|
+
end
|
529
|
+
|
530
|
+
@data = data.map {|complex| complex.real }
|
531
|
+
return self
|
532
|
+
end
|
533
|
+
end
|
451
534
|
end
|
@@ -3,6 +3,11 @@ module SPCore
|
|
3
3
|
# @author James Tunnell
|
4
4
|
class FFT
|
5
5
|
|
6
|
+
def self.power_of_two? size
|
7
|
+
log2size = Math::log2(size)
|
8
|
+
return log2size.floor == log2size
|
9
|
+
end
|
10
|
+
|
6
11
|
# Forward Radix-2 FFT transform using decimation-in-time. Operates on an array
|
7
12
|
# of real values, representing a time domain signal.
|
8
13
|
# Ported from unlicensed MATLAB code which was posted to the MathWorks file
|
data/lib/spcore/util/plotter.rb
CHANGED
data/lib/spcore/version.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
module SPCore
|
2
|
+
# Produces a Tukey window of a given size (number of samples).
|
3
|
+
# The Tukey window, also known as tapered cosine, can be regarded as a cosine
|
4
|
+
# lobe of width alpha * N / 2 that is convolved with a rectangular window. At
|
5
|
+
# alpha = 0 it becomes rectangular, and at alpha = 1 it becomes a Hann window.
|
6
|
+
# For more info, see https://en.wikipedia.org/wiki/Window_function#Tukey_window.
|
7
|
+
class TukeyWindow
|
8
|
+
attr_reader :data
|
9
|
+
def initialize size, alpha = 0.5
|
10
|
+
@data = Array.new(size)
|
11
|
+
|
12
|
+
left = (alpha * (size - 1) / 2.0).to_i
|
13
|
+
right = ((size - 1) * (1.0 - (alpha / 2.0))).to_i
|
14
|
+
|
15
|
+
size_min_1 = size - 1
|
16
|
+
|
17
|
+
for n in 0...left
|
18
|
+
x = Math::PI * (((2.0 * n) / (alpha * size_min_1)) - 1.0)
|
19
|
+
@data[n] = 0.5 * (1.0 + Math::cos(x))
|
20
|
+
end
|
21
|
+
|
22
|
+
for n in left..right
|
23
|
+
@data[n] = 1.0
|
24
|
+
end
|
25
|
+
|
26
|
+
for n in (right + 1)...size
|
27
|
+
x = Math::PI * (((2 * n) / (alpha * size_min_1)) - (2.0 / alpha) + 1.0)
|
28
|
+
@data[n] = 0.5 * (1.0 + Math::cos(x))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe SPCore::Envelope do
|
4
|
+
it 'should produce an output that follows the amplitude of the input' do
|
5
|
+
sample_rate = 1000
|
6
|
+
sample_count = 512
|
7
|
+
generator = SignalGenerator.new(:size => sample_count, :sample_rate => sample_rate)
|
8
|
+
|
9
|
+
modulation_signal = generator.make_signal [4.0]
|
10
|
+
modulation_signal.multiply! BlackmanWindow.new(sample_count).data
|
11
|
+
|
12
|
+
base_signal = generator.make_signal [64.0]
|
13
|
+
base_signal.multiply! modulation_signal
|
14
|
+
|
15
|
+
envelope = base_signal.envelope(true)
|
16
|
+
|
17
|
+
#signals = {
|
18
|
+
# "signal" => base_signal,
|
19
|
+
# "modulation" => modulation_signal,
|
20
|
+
# "envelope" => envelope,
|
21
|
+
#}
|
22
|
+
#
|
23
|
+
#Plotter.new(
|
24
|
+
# :title => "signal and envelope",
|
25
|
+
# :xlabel => "sample",
|
26
|
+
# :ylabel => "values",
|
27
|
+
#).plot_signals(signals)
|
28
|
+
|
29
|
+
begin
|
30
|
+
ideal = modulation_signal.energy
|
31
|
+
actual = envelope.energy
|
32
|
+
error = (ideal - actual).abs / ideal
|
33
|
+
error.should be_within(0.1).of(0.0)
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
ideal = modulation_signal.rms
|
38
|
+
actual = envelope.rms
|
39
|
+
error = (ideal - actual).abs / ideal
|
40
|
+
error.should be_within(0.1).of(0.0)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
File without changes
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe SPCore::Signal do
|
4
|
+
describe '#remove_frequencies' do
|
5
|
+
before :each do
|
6
|
+
generator = SignalGenerator.new(:sample_rate => 2000, :size => 256)
|
7
|
+
@original = generator.make_signal([40.0, 160.0])
|
8
|
+
@ideal_modified = generator.make_signal([160.0])
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should produce the expected modified signal (with almost identical energy and RMS)' do
|
12
|
+
modified = @original.remove_frequencies(0.0..80.0)
|
13
|
+
|
14
|
+
begin
|
15
|
+
actual = modified.energy
|
16
|
+
ideal = @ideal_modified.energy
|
17
|
+
error = (actual - ideal).abs / ideal
|
18
|
+
error.should be_within(0.05).of(0.0)
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
actual = modified.rms
|
23
|
+
ideal = @ideal_modified.rms
|
24
|
+
error = (actual - ideal).abs / ideal
|
25
|
+
error.should be_within(0.05).of(0.0)
|
26
|
+
end
|
27
|
+
|
28
|
+
#Plotter.new().plot_signals(
|
29
|
+
# #"original" => @original,
|
30
|
+
# "ideal modified" => @ideal_modified,
|
31
|
+
# "actual modified" => modified
|
32
|
+
#)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#keep_frequencies' do
|
37
|
+
before :each do
|
38
|
+
generator = SignalGenerator.new(:sample_rate => 2000, :size => 256)
|
39
|
+
@original = generator.make_signal([40.0, 160.0])
|
40
|
+
@ideal_modified = generator.make_signal([160.0])
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should produce the expected modified signal (with almost identical energy and RMS)' do
|
44
|
+
modified = @original.keep_frequencies(80.0..240.0)
|
45
|
+
|
46
|
+
begin
|
47
|
+
actual = modified.energy
|
48
|
+
ideal = @ideal_modified.energy
|
49
|
+
error = (actual - ideal).abs / ideal
|
50
|
+
error.should be_within(0.05).of(0.0)
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
actual = modified.rms
|
55
|
+
ideal = @ideal_modified.rms
|
56
|
+
error = (actual - ideal).abs / ideal
|
57
|
+
error.should be_within(0.05).of(0.0)
|
58
|
+
end
|
59
|
+
|
60
|
+
#Plotter.new().plot_signals(
|
61
|
+
# #"original" => @original,
|
62
|
+
# "ideal modified" => @ideal_modified,
|
63
|
+
# "actual modified" => modified
|
64
|
+
#)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/spec/windows/window_spec.rb
CHANGED
@@ -5,20 +5,21 @@ describe 'windows' do
|
|
5
5
|
size = 512
|
6
6
|
|
7
7
|
window_classes = [
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
8
|
+
#RectangularWindow,
|
9
|
+
#HannWindow,
|
10
|
+
#HammingWindow,
|
11
|
+
#CosineWindow,
|
12
|
+
#LanczosWindow,
|
13
|
+
#TriangularWindow,
|
14
|
+
#BartlettWindow,
|
15
|
+
#GaussianWindow,
|
16
|
+
#BartlettHannWindow,
|
17
|
+
#BlackmanWindow,
|
18
|
+
#NuttallWindow,
|
19
|
+
#BlackmanHarrisWindow,
|
20
|
+
#BlackmanNuttallWindow,
|
21
|
+
#FlatTopWindow,
|
22
|
+
#TukeyWindow
|
22
23
|
]
|
23
24
|
|
24
25
|
windows = {}
|
@@ -27,7 +28,7 @@ describe 'windows' do
|
|
27
28
|
end
|
28
29
|
|
29
30
|
if windows.any?
|
30
|
-
Plotter.new(:title => "windows").plot_1d
|
31
|
+
Plotter.new(:title => "windows").plot_1d(windows, true)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spcore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hashmake
|
@@ -144,13 +144,13 @@ files:
|
|
144
144
|
- README.rdoc
|
145
145
|
- Rakefile
|
146
146
|
- lib/spcore.rb
|
147
|
+
- lib/spcore/analysis/envelope.rb
|
148
|
+
- lib/spcore/analysis/extrema.rb
|
149
|
+
- lib/spcore/analysis/signal.rb
|
147
150
|
- lib/spcore/core/circular_buffer.rb
|
148
151
|
- lib/spcore/core/constants.rb
|
149
152
|
- lib/spcore/core/delay_line.rb
|
150
|
-
- lib/spcore/core/envelope.rb
|
151
|
-
- lib/spcore/core/extrema.rb
|
152
153
|
- lib/spcore/core/oscillator.rb
|
153
|
-
- lib/spcore/core/signal.rb
|
154
154
|
- lib/spcore/filters/fir/dual_sinc_filter.rb
|
155
155
|
- lib/spcore/filters/fir/fir.rb
|
156
156
|
- lib/spcore/filters/fir/sinc_filter.rb
|
@@ -188,11 +188,13 @@ files:
|
|
188
188
|
- lib/spcore/windows/nuttall_window.rb
|
189
189
|
- lib/spcore/windows/rectangular_window.rb
|
190
190
|
- lib/spcore/windows/triangular_window.rb
|
191
|
+
- lib/spcore/windows/tukey_window.rb
|
191
192
|
- spcore.gemspec
|
193
|
+
- spec/analysis/envelope_spec.rb
|
194
|
+
- spec/analysis/extrema_spec.rb
|
195
|
+
- spec/analysis/signal_spec.rb
|
192
196
|
- spec/core/circular_buffer_spec.rb
|
193
197
|
- spec/core/delay_line_spec.rb
|
194
|
-
- spec/core/envelope_spec.rb
|
195
|
-
- spec/core/extrema_spec.rb
|
196
198
|
- spec/core/oscillator_spec.rb
|
197
199
|
- spec/filters/fir/dual_sinc_filter_spec.rb
|
198
200
|
- spec/filters/fir/sinc_filter_spec.rb
|
@@ -226,7 +228,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
226
228
|
version: '0'
|
227
229
|
segments:
|
228
230
|
- 0
|
229
|
-
hash: -
|
231
|
+
hash: -94862182169118755
|
230
232
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
231
233
|
none: false
|
232
234
|
requirements:
|
@@ -235,7 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
235
237
|
version: '0'
|
236
238
|
segments:
|
237
239
|
- 0
|
238
|
-
hash: -
|
240
|
+
hash: -94862182169118755
|
239
241
|
requirements: []
|
240
242
|
rubyforge_project:
|
241
243
|
rubygems_version: 1.8.23
|
@@ -243,10 +245,11 @@ signing_key:
|
|
243
245
|
specification_version: 3
|
244
246
|
summary: A library of signal processing methods and classes.
|
245
247
|
test_files:
|
248
|
+
- spec/analysis/envelope_spec.rb
|
249
|
+
- spec/analysis/extrema_spec.rb
|
250
|
+
- spec/analysis/signal_spec.rb
|
246
251
|
- spec/core/circular_buffer_spec.rb
|
247
252
|
- spec/core/delay_line_spec.rb
|
248
|
-
- spec/core/envelope_spec.rb
|
249
|
-
- spec/core/extrema_spec.rb
|
250
253
|
- spec/core/oscillator_spec.rb
|
251
254
|
- spec/filters/fir/dual_sinc_filter_spec.rb
|
252
255
|
- spec/filters/fir/sinc_filter_spec.rb
|
data/spec/core/envelope_spec.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe SPCore::Envelope do
|
4
|
-
it 'should produce an output that follows the amplitude of the input' do
|
5
|
-
sample_rate = 400
|
6
|
-
sample_count = sample_rate
|
7
|
-
generator = SignalGenerator.new(:size => sample_count, :sample_rate => sample_rate)
|
8
|
-
base_signal = generator.make_signal [sample_rate / 5.0]
|
9
|
-
modulation_signal = generator.make_signal [sample_rate / 80.0]
|
10
|
-
base_signal.multiply! modulation_signal
|
11
|
-
|
12
|
-
envelope = base_signal.envelope
|
13
|
-
|
14
|
-
Plotter.new(
|
15
|
-
:title => "signal and envelope",
|
16
|
-
:xlabel => "sample",
|
17
|
-
:ylabel => "values",
|
18
|
-
).plot_1d(
|
19
|
-
"signal" => base_signal.data,
|
20
|
-
"modulation" => modulation_signal.data,
|
21
|
-
"envelope" => envelope.data,
|
22
|
-
)
|
23
|
-
|
24
|
-
max_diff = 0.1
|
25
|
-
sample_count.times do |n|
|
26
|
-
envelope.data[n].should be_within(max_diff).of(modulation_signal.data[n].abs)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|