spcore 0.2.0 → 0.2.1
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 +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
|