spcore 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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