spcore 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.rdoc +5 -1
- data/lib/spcore.rb +9 -6
- data/lib/spcore/analysis/calculus.rb +38 -0
- data/lib/spcore/analysis/features.rb +186 -0
- data/lib/spcore/analysis/frequency_domain.rb +191 -0
- data/lib/spcore/analysis/{correlation.rb → statistics.rb} +41 -18
- data/lib/spcore/core/delay_line.rb +1 -1
- data/lib/spcore/filters/fir/dual_sinc_filter.rb +1 -1
- data/lib/spcore/filters/fir/sinc_filter.rb +1 -1
- data/lib/spcore/generation/comb_filter.rb +65 -0
- data/lib/spcore/{core → generation}/oscillator.rb +1 -1
- data/lib/spcore/{util → generation}/signal_generator.rb +1 -1
- data/lib/spcore/util/envelope_detector.rb +1 -1
- data/lib/spcore/util/gain.rb +9 -167
- data/lib/spcore/util/plotter.rb +1 -1
- data/lib/spcore/{analysis → util}/signal.rb +116 -127
- data/lib/spcore/version.rb +1 -1
- data/spcore.gemspec +2 -0
- data/spec/analysis/calculus_spec.rb +54 -0
- data/spec/analysis/features_spec.rb +106 -0
- data/spec/analysis/frequency_domain_spec.rb +147 -0
- data/spec/analysis/piano_C4.wav +0 -0
- data/spec/analysis/statistics_spec.rb +61 -0
- data/spec/analysis/trumpet_B4.wav +0 -0
- data/spec/generation/comb_filter_spec.rb +37 -0
- data/spec/{core → generation}/oscillator_spec.rb +0 -0
- data/spec/{util → generation}/signal_generator_spec.rb +0 -0
- data/spec/interpolation/interpolation_spec.rb +0 -2
- data/spec/{analysis → util}/signal_spec.rb +1 -35
- metadata +64 -22
- data/lib/spcore/analysis/envelope.rb +0 -76
- data/lib/spcore/analysis/extrema.rb +0 -55
- data/spec/analysis/correlation_spec.rb +0 -28
- data/spec/analysis/envelope_spec.rb +0 -50
- data/spec/analysis/extrema_spec.rb +0 -42
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.2.
|
4
|
+
version: 0.2.1
|
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-
|
12
|
+
date: 2013-07-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hashmake
|
@@ -123,6 +123,38 @@ dependencies:
|
|
123
123
|
- - ! '>='
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: pry-nav
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: wavefile
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
126
158
|
description: ! 'A library of signal processing methods and classes for resampling,
|
127
159
|
frequency domain transform (FFT and DFT), windowing (Blackman, Hamming, etc.), FIR
|
128
160
|
filtering (windowed sinc), interpolation (linear and polynomial), plotting data,
|
@@ -144,14 +176,13 @@ files:
|
|
144
176
|
- README.rdoc
|
145
177
|
- Rakefile
|
146
178
|
- lib/spcore.rb
|
147
|
-
- lib/spcore/analysis/
|
148
|
-
- lib/spcore/analysis/
|
149
|
-
- lib/spcore/analysis/
|
150
|
-
- lib/spcore/analysis/
|
179
|
+
- lib/spcore/analysis/calculus.rb
|
180
|
+
- lib/spcore/analysis/features.rb
|
181
|
+
- lib/spcore/analysis/frequency_domain.rb
|
182
|
+
- lib/spcore/analysis/statistics.rb
|
151
183
|
- lib/spcore/core/circular_buffer.rb
|
152
184
|
- lib/spcore/core/constants.rb
|
153
185
|
- lib/spcore/core/delay_line.rb
|
154
|
-
- lib/spcore/core/oscillator.rb
|
155
186
|
- lib/spcore/filters/fir/dual_sinc_filter.rb
|
156
187
|
- lib/spcore/filters/fir/fir.rb
|
157
188
|
- lib/spcore/filters/fir/sinc_filter.rb
|
@@ -161,6 +192,9 @@ files:
|
|
161
192
|
- lib/spcore/filters/iir/cookbook_highpass_filter.rb
|
162
193
|
- lib/spcore/filters/iir/cookbook_lowpass_filter.rb
|
163
194
|
- lib/spcore/filters/iir/cookbook_notch_filter.rb
|
195
|
+
- lib/spcore/generation/comb_filter.rb
|
196
|
+
- lib/spcore/generation/oscillator.rb
|
197
|
+
- lib/spcore/generation/signal_generator.rb
|
164
198
|
- lib/spcore/interpolation/interpolation.rb
|
165
199
|
- lib/spcore/resampling/discrete_resampling.rb
|
166
200
|
- lib/spcore/resampling/hybrid_resampling.rb
|
@@ -173,7 +207,7 @@ files:
|
|
173
207
|
- lib/spcore/util/plotter.rb
|
174
208
|
- lib/spcore/util/saturation.rb
|
175
209
|
- lib/spcore/util/scale.rb
|
176
|
-
- lib/spcore/util/
|
210
|
+
- lib/spcore/util/signal.rb
|
177
211
|
- lib/spcore/version.rb
|
178
212
|
- lib/spcore/windows/bartlett_hann_window.rb
|
179
213
|
- lib/spcore/windows/bartlett_window.rb
|
@@ -191,16 +225,20 @@ files:
|
|
191
225
|
- lib/spcore/windows/triangular_window.rb
|
192
226
|
- lib/spcore/windows/tukey_window.rb
|
193
227
|
- spcore.gemspec
|
194
|
-
- spec/analysis/
|
195
|
-
- spec/analysis/
|
196
|
-
- spec/analysis/
|
197
|
-
- spec/analysis/
|
228
|
+
- spec/analysis/calculus_spec.rb
|
229
|
+
- spec/analysis/features_spec.rb
|
230
|
+
- spec/analysis/frequency_domain_spec.rb
|
231
|
+
- spec/analysis/piano_C4.wav
|
232
|
+
- spec/analysis/statistics_spec.rb
|
233
|
+
- spec/analysis/trumpet_B4.wav
|
198
234
|
- spec/core/circular_buffer_spec.rb
|
199
235
|
- spec/core/delay_line_spec.rb
|
200
|
-
- spec/core/oscillator_spec.rb
|
201
236
|
- spec/filters/fir/dual_sinc_filter_spec.rb
|
202
237
|
- spec/filters/fir/sinc_filter_spec.rb
|
203
238
|
- spec/filters/iir/cookbook_filter_spec.rb
|
239
|
+
- spec/generation/comb_filter_spec.rb
|
240
|
+
- spec/generation/oscillator_spec.rb
|
241
|
+
- spec/generation/signal_generator_spec.rb
|
204
242
|
- spec/interpolation/interpolation_spec.rb
|
205
243
|
- spec/resampling/discrete_resampling_spec.rb
|
206
244
|
- spec/resampling/hybrid_resampling_spec.rb
|
@@ -213,7 +251,7 @@ files:
|
|
213
251
|
- spec/util/gain_spec.rb
|
214
252
|
- spec/util/limiters_spec.rb
|
215
253
|
- spec/util/saturate_spec.rb
|
216
|
-
- spec/util/
|
254
|
+
- spec/util/signal_spec.rb
|
217
255
|
- spec/windows/window_spec.rb
|
218
256
|
homepage: https://github.com/jamestunnell/spcore
|
219
257
|
licenses:
|
@@ -230,7 +268,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
230
268
|
version: '0'
|
231
269
|
segments:
|
232
270
|
- 0
|
233
|
-
hash:
|
271
|
+
hash: 2316548233979027039
|
234
272
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
235
273
|
none: false
|
236
274
|
requirements:
|
@@ -239,7 +277,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
239
277
|
version: '0'
|
240
278
|
segments:
|
241
279
|
- 0
|
242
|
-
hash:
|
280
|
+
hash: 2316548233979027039
|
243
281
|
requirements: []
|
244
282
|
rubyforge_project:
|
245
283
|
rubygems_version: 1.8.23
|
@@ -247,16 +285,20 @@ signing_key:
|
|
247
285
|
specification_version: 3
|
248
286
|
summary: A library of signal processing methods and classes.
|
249
287
|
test_files:
|
250
|
-
- spec/analysis/
|
251
|
-
- spec/analysis/
|
252
|
-
- spec/analysis/
|
253
|
-
- spec/analysis/
|
288
|
+
- spec/analysis/calculus_spec.rb
|
289
|
+
- spec/analysis/features_spec.rb
|
290
|
+
- spec/analysis/frequency_domain_spec.rb
|
291
|
+
- spec/analysis/piano_C4.wav
|
292
|
+
- spec/analysis/statistics_spec.rb
|
293
|
+
- spec/analysis/trumpet_B4.wav
|
254
294
|
- spec/core/circular_buffer_spec.rb
|
255
295
|
- spec/core/delay_line_spec.rb
|
256
|
-
- spec/core/oscillator_spec.rb
|
257
296
|
- spec/filters/fir/dual_sinc_filter_spec.rb
|
258
297
|
- spec/filters/fir/sinc_filter_spec.rb
|
259
298
|
- spec/filters/iir/cookbook_filter_spec.rb
|
299
|
+
- spec/generation/comb_filter_spec.rb
|
300
|
+
- spec/generation/oscillator_spec.rb
|
301
|
+
- spec/generation/signal_generator_spec.rb
|
260
302
|
- spec/interpolation/interpolation_spec.rb
|
261
303
|
- spec/resampling/discrete_resampling_spec.rb
|
262
304
|
- spec/resampling/hybrid_resampling_spec.rb
|
@@ -269,6 +311,6 @@ test_files:
|
|
269
311
|
- spec/util/gain_spec.rb
|
270
312
|
- spec/util/limiters_spec.rb
|
271
313
|
- spec/util/saturate_spec.rb
|
272
|
-
- spec/util/
|
314
|
+
- spec/util/signal_spec.rb
|
273
315
|
- spec/windows/window_spec.rb
|
274
316
|
has_rdoc:
|
@@ -1,76 +0,0 @@
|
|
1
|
-
module SPCore
|
2
|
-
# Determines the envelope of given samples. Unlike the EnvelopeDetector, this
|
3
|
-
# operates on a signal (time-series data) after it is recieved, not
|
4
|
-
# sample-by-sample. As a result, it provides the envelope as an entire signal.
|
5
|
-
#
|
6
|
-
# @author James Tunnell
|
7
|
-
class Envelope
|
8
|
-
|
9
|
-
attr_reader :data
|
10
|
-
|
11
|
-
def initialize samples
|
12
|
-
# combine absolute values of positive maxima and negative minima
|
13
|
-
extrema = Extrema.new(samples)
|
14
|
-
starting_outline = {}
|
15
|
-
extrema.minima.each do |idx,val|
|
16
|
-
if val <= 0.0
|
17
|
-
starting_outline[idx] =val.abs
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
extrema.maxima.each do |idx,val|
|
22
|
-
if val >= 0.0
|
23
|
-
starting_outline[idx] = val.abs
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# add in first and last samples so the envelope follows entire signal
|
28
|
-
starting_outline[0] = samples[0].abs
|
29
|
-
starting_outline[samples.count - 1] = samples[samples.count - 1].abs
|
30
|
-
|
31
|
-
# the extrema we have now are probably not spaced evenly. Upsampling at
|
32
|
-
# this point would lead to a time-distorted signal. So the next step is to
|
33
|
-
# interpolate between all the extrema to make a crude but properly sampled
|
34
|
-
# envelope.
|
35
|
-
|
36
|
-
proper_outline = Array.new(samples.count, 0)
|
37
|
-
indices = starting_outline.keys.sort
|
38
|
-
|
39
|
-
for i in 1...indices.count
|
40
|
-
l_idx = indices[i-1]
|
41
|
-
r_idx = indices[i]
|
42
|
-
|
43
|
-
l_val = starting_outline[l_idx]
|
44
|
-
r_val = starting_outline[r_idx]
|
45
|
-
|
46
|
-
proper_outline[l_idx] = l_val
|
47
|
-
proper_outline[r_idx] = r_val
|
48
|
-
|
49
|
-
idx_span = r_idx - l_idx
|
50
|
-
|
51
|
-
for j in (l_idx + 1)...(r_idx)
|
52
|
-
x = (j - l_idx).to_f / idx_span
|
53
|
-
y = Interpolation.linear l_val, r_val, x
|
54
|
-
proper_outline[j] = y
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# Now downsample by dropping samples, back to the number of starting_outline we had
|
59
|
-
# with just the extrema, but this time with samples properly spaced so as
|
60
|
-
# to avoid time distortion after upsampling.
|
61
|
-
|
62
|
-
downsample_factor = (samples.count / starting_outline.count).to_i
|
63
|
-
downsampled_outline = []
|
64
|
-
|
65
|
-
(0...proper_outline.count).step(downsample_factor) do |n|
|
66
|
-
downsampled_outline.push proper_outline[n]
|
67
|
-
end
|
68
|
-
|
69
|
-
# finally, use polynomial interpolation to upsample to the original sample rate.
|
70
|
-
|
71
|
-
upsample_factor = samples.count / downsampled_outline.count.to_f
|
72
|
-
@data = PolynomialResampling.upsample(downsampled_outline, upsample_factor)
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
module SPCore
|
2
|
-
# Finds extrema (minima and maxima).
|
3
|
-
class Extrema
|
4
|
-
|
5
|
-
attr_reader :minima, :maxima, :extrema
|
6
|
-
|
7
|
-
def initialize samples
|
8
|
-
@minima = {}
|
9
|
-
@maxima = {}
|
10
|
-
|
11
|
-
global_min_idx = 0
|
12
|
-
global_min_val = samples[0]
|
13
|
-
global_max_idx = 0
|
14
|
-
global_max_val = samples[0]
|
15
|
-
|
16
|
-
diffs = []
|
17
|
-
for i in (1...samples.count)
|
18
|
-
diffs.push(samples[i] - samples[i-1])
|
19
|
-
|
20
|
-
if samples[i] < global_min_val
|
21
|
-
global_min_idx = i
|
22
|
-
global_min_val = samples[i]
|
23
|
-
end
|
24
|
-
|
25
|
-
if samples[i] > global_max_val
|
26
|
-
global_max_idx = i
|
27
|
-
global_max_val = samples[i]
|
28
|
-
end
|
29
|
-
end
|
30
|
-
@minima[global_min_idx] = global_min_val
|
31
|
-
@maxima[global_max_idx] = global_max_val
|
32
|
-
|
33
|
-
is_positive = diffs.first > 0.0 # starting off with positive difference?
|
34
|
-
|
35
|
-
# at zero crossings there is a local maxima/minima
|
36
|
-
for i in (1...diffs.count)
|
37
|
-
if is_positive
|
38
|
-
# at positive-to-negative transition there is a local maxima
|
39
|
-
if diffs[i] <= 0.0
|
40
|
-
@maxima[i] = samples[i]
|
41
|
-
is_positive = false
|
42
|
-
end
|
43
|
-
else
|
44
|
-
# at negative-to-positive transition there is a local minima
|
45
|
-
if diffs[i] > 0.0
|
46
|
-
@minima[i] = samples[i]
|
47
|
-
is_positive = true
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
@extrema = @minima.merge(@maxima)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe SPCore::Correlation do
|
4
|
-
context 'image => triangular window' do
|
5
|
-
before :all do
|
6
|
-
@size = size = 48
|
7
|
-
@triangle = TriangularWindow.new(size * 2).data
|
8
|
-
end
|
9
|
-
|
10
|
-
context 'feature => rising ramp (half size of triangular window)' do
|
11
|
-
it 'should have maximum correlation at beginning' do
|
12
|
-
rising_ramp = Array.new(@size){|i| i / @size.to_f }
|
13
|
-
correlation = Correlation.new(@triangle, rising_ramp)
|
14
|
-
correlation.data.first.should eq(correlation.data.max)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
context 'feature => falling ramp (half size of triangular window)' do
|
19
|
-
it 'should have maximum correlation at end' do
|
20
|
-
falling_ramp = Array.new(@size){|i| (@size - i) / @size.to_f }
|
21
|
-
correlation = Correlation.new(@triangle, falling_ramp)
|
22
|
-
correlation.data.last.should eq(correlation.data.max)
|
23
|
-
|
24
|
-
#Plotter.plot_1d "correlate falling ramp" => correlation.data
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe SPCore::Envelope do
|
4
|
-
before :all do
|
5
|
-
sample_rate = 1000
|
6
|
-
sample_count = 512 * 2
|
7
|
-
generator = SignalGenerator.new(:size => sample_count, :sample_rate => sample_rate)
|
8
|
-
|
9
|
-
@modulation_signal = generator.make_signal [4.0], :amplitude => 0.1
|
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
|
-
end
|
15
|
-
|
16
|
-
it 'should produce an output that follows the amplitude of the input' do
|
17
|
-
envelope = @base_signal.envelope
|
18
|
-
check_envelope(envelope)
|
19
|
-
end
|
20
|
-
|
21
|
-
def check_envelope envelope
|
22
|
-
#signals = {
|
23
|
-
# "signal" => @base_signal,
|
24
|
-
# "modulation (abs)" => @modulation_signal.abs,
|
25
|
-
# "envelope" => envelope,
|
26
|
-
#}
|
27
|
-
#
|
28
|
-
#Plotter.new(
|
29
|
-
# :title => "signal and envelope",
|
30
|
-
# :xlabel => "sample",
|
31
|
-
# :ylabel => "values",
|
32
|
-
#).plot_signals(signals)
|
33
|
-
|
34
|
-
#Plotter.new().plot_2d("envelop freq magnitudes" => envelope.freq_magnitudes)
|
35
|
-
|
36
|
-
begin
|
37
|
-
ideal = @modulation_signal.energy
|
38
|
-
actual = envelope.energy
|
39
|
-
error = (ideal - actual).abs / ideal
|
40
|
-
error.should be_within(0.1).of(0.0)
|
41
|
-
end
|
42
|
-
|
43
|
-
begin
|
44
|
-
ideal = @modulation_signal.rms
|
45
|
-
actual = envelope.rms
|
46
|
-
error = (ideal - actual).abs / ideal
|
47
|
-
error.should be_within(0.1).of(0.0)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe SPCore::Extrema do
|
4
|
-
context '#minima' do
|
5
|
-
it 'should return points where local and global minima occur' do
|
6
|
-
cases = {
|
7
|
-
[3.8, 3.0, 2.9, 2.95, 3.6, 3.4, 2.8, 2.3, 2.1, 2.0, 2.5] => { 2 => 2.9, 9 => 2.0 },
|
8
|
-
[3.2, 3.5, 2.9, 2.7, 2.8, 2.7, 2.5, 2.2, 2.4, 2.3, 2.0] => { 3 => 2.7, 7 => 2.2, 10 => 2.0 },
|
9
|
-
}
|
10
|
-
|
11
|
-
cases.each do |samples, minima|
|
12
|
-
Extrema.new(samples).minima.should eq minima
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context '#maxima' do
|
18
|
-
it 'should return points where local and global maxima occur' do
|
19
|
-
cases = {
|
20
|
-
[3.8, 3.0, 2.9, 2.95, 3.6, 3.4, 2.8, 2.3, 2.1, 2.0, 2.5] => { 0 => 3.8, 4 => 3.6 },
|
21
|
-
[3.2, 3.5, 2.9, 2.7, 2.8, 2.7, 2.5, 2.2, 2.4, 2.3, 2.0] => { 1 => 3.5, 4 => 2.8, 8 => 2.4},
|
22
|
-
}
|
23
|
-
|
24
|
-
cases.each do |samples, maxima|
|
25
|
-
Extrema.new(samples).maxima.should eq maxima
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
context '#extrema' do
|
31
|
-
it 'should return points where local and global extrema occur' do
|
32
|
-
cases = {
|
33
|
-
[3.8, 3.0, 2.9, 2.95, 3.6, 3.4, 2.8, 2.3, 2.1, 2.0, 2.5] => { 0 => 3.8, 2 => 2.9, 4 => 3.6, 9 => 2.0},
|
34
|
-
[3.2, 3.5, 2.9, 2.7, 2.8, 2.7, 2.5, 2.2, 2.4, 2.3, 2.0] => { 1 => 3.5, 3 => 2.7, 4 => 2.8, 7 => 2.2, 8 => 2.4, 10 => 2.0},
|
35
|
-
}
|
36
|
-
|
37
|
-
cases.each do |samples, extrema|
|
38
|
-
Extrema.new(samples).extrema.should eq extrema
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|