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.
@@ -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.
@@ -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, etc.)
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)
@@ -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'
@@ -4,7 +4,7 @@ module SPCore
4
4
  # sample-by-sample. As a result, it provides the envelope as an entire signal.
5
5
  #
6
6
  # @author James Tunnell
7
- class Envelope < Signal
7
+ class Envelope
8
8
 
9
9
  attr_reader :data
10
10
 
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
- # Determine the envelope of the current Signal and return the resulting data
138
- # in a new Signal object.
139
- def envelope
140
- return Envelope.new(@data)
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
- new_data = Array.new(@data.size)
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
- new_data = Array.new(@data.size)
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
- new_data = Array.new(@data.size)
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
- new_data = Array.new(@data.size)
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
@@ -98,5 +98,13 @@ class Plotter
98
98
  end
99
99
  end
100
100
  end
101
+
102
+ def plot_signals signals_hash
103
+ data_hash = {}
104
+ signals_hash.each do |name, signal|
105
+ data_hash[name] = signal.data
106
+ end
107
+ plot_1d data_hash
108
+ end
101
109
  end
102
110
  end
@@ -1,5 +1,5 @@
1
1
  # A library of signal processing methods and classes.
2
2
  module SPCore
3
3
  # spcore version
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.9"
5
5
  end
@@ -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
@@ -5,20 +5,21 @@ describe 'windows' do
5
5
  size = 512
6
6
 
7
7
  window_classes = [
8
- #SPCore::RectangularWindow,
9
- #SPCore::HannWindow,
10
- #SPCore::HammingWindow,
11
- #SPCore::CosineWindow,
12
- #SPCore::LanczosWindow,
13
- #SPCore::TriangularWindow,
14
- #SPCore::BartlettWindow,
15
- #SPCore::GaussianWindow,
16
- #SPCore::BartlettHannWindow,
17
- #SPCore::BlackmanWindow,
18
- #SPCore::NuttallWindow,
19
- #SPCore::BlackmanHarrisWindow,
20
- #SPCore::BlackmanNuttallWindow,
21
- #SPCore::FlatTopWindow
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 windows
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.8
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-04 00:00:00.000000000 Z
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: -1351761362878137001
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: -1351761362878137001
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
@@ -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