spcore 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ module SPCore
2
+ class Saturation
3
+ # Sigmoid-based saturation when input is above threshold.
4
+ # From musicdsp.org, posted by Bram.
5
+ def self.sigmoid input, threshold
6
+ input_abs = input.abs
7
+ if input_abs < threshold
8
+ return input
9
+ else
10
+ #y = threshold + (1.0 - threshold) * mock_sigmoid((input_abs - threshold) / ((1.0 - threshold) * 1.5))
11
+ y = threshold + (1.0 - threshold) * Math::tanh((input_abs - threshold)/(1-threshold))
12
+
13
+ if input > 0.0
14
+ return y
15
+ else
16
+ return -y
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.gompertz input, threshold
22
+ a = threshold
23
+ b = -4
24
+ c = -2
25
+ x = input.abs
26
+ y = 2 * a * Math::exp(b * Math::exp(c * x))
27
+
28
+ if input > 0.0
29
+ return y
30
+ else
31
+ return -y
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ #def self.mock_sigmoid x
38
+ # if(x.abs < 1.0)
39
+ # return x * (1.5 - 0.5 * x * x)
40
+ # else
41
+ # return x > 0.0 ? 1.0 : -1.0
42
+ # end
43
+ #end
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ module SPCore
2
+ # spcore version
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/spcore/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "spcore"
7
+ gem.version = SPCore::VERSION
8
+ gem.summary = %q{Perform basic signal processing functions (delay line, filters, envelope detection, etc...).}
9
+ gem.description = <<DESCRIPTION
10
+ Contains core signal processing functions, including:
11
+ Delay line
12
+ Biquad filters
13
+ Envelope detector
14
+ Conversion from dB-linear and linear-dB
15
+ Linear interpolation
16
+ Oscillator with selectable wave type (sine, square, triangle, sawtooth)
17
+
18
+ DESCRIPTION
19
+ gem.license = "MIT"
20
+ gem.authors = ["James Tunnell"]
21
+ gem.email = "jamestunnell@lavabit.com"
22
+ gem.homepage = "https://rubygems.org/gems/spcore"
23
+
24
+ gem.files = `git ls-files`.split($/)
25
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
26
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
27
+ gem.require_paths = ['lib']
28
+
29
+ gem.add_dependency 'hashmake'
30
+
31
+ gem.add_development_dependency 'bundler', '~> 1.0'
32
+ gem.add_development_dependency 'rake', '~> 0.8'
33
+ gem.add_development_dependency 'rspec', '~> 2.4'
34
+ gem.add_development_dependency 'yard', '~> 0.8'
35
+ gem.add_development_dependency 'pry'
36
+ gem.add_development_dependency 'gnuplot'
37
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SPCore::Limiters do
4
+ describe '.make_no_limiter' do
5
+ it 'should make a lambda that does not limit values' do
6
+ limiter = SPCore::Limiters.make_no_limiter
7
+ limiter.call(Float::MAX).should eq(Float::MAX)
8
+ limiter.call(-Float::MAX).should eq(-Float::MAX)
9
+ limiter.call(Float::MIN).should eq(Float::MIN)
10
+ end
11
+ end
12
+
13
+ describe '.make_lower_limiter' do
14
+ it 'should make a lambda that limits values to be above the limit value' do
15
+ limiter = SPCore::Limiters.make_lower_limiter 5.0
16
+ limiter.call(4.5).should eq(5.0)
17
+ limiter.call(5.5).should eq(5.5)
18
+ end
19
+ end
20
+
21
+ describe 'make_upper_limiter' do
22
+ it 'should make a lambda that limits values to be above the limit value' do
23
+ limiter = SPCore::Limiters.make_upper_limiter 5.0
24
+ limiter.call(5.5).should eq(5.0)
25
+ limiter.call(4.5).should eq(4.5)
26
+ end
27
+ end
28
+
29
+ describe '.make_range_limiter' do
30
+ it 'should make a lambda that limits values to be between the limit range' do
31
+ limiter = SPCore::Limiters.make_range_limiter(2.5..5.0)
32
+ limiter.call(1.5).should eq(2.5)
33
+ limiter.call(5.5).should eq(5.0)
34
+ limiter.call(3.0).should eq(3.0)
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,177 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SPCore::CircularBuffer do
4
+ context '.new' do
5
+ it 'should set buffer size but fill count should be zero' do
6
+ [0,20,100].each do |size|
7
+ buffer = SPCore::CircularBuffer.new size
8
+ buffer.empty?.should be_true
9
+ buffer.size.should eq(size)
10
+ end
11
+ end
12
+ end
13
+
14
+ describe '#push' do
15
+ it 'should report full after buffer.size calls to #push' do
16
+ [0,20,100].each do |size|
17
+ buffer = SPCore::CircularBuffer.new size
18
+
19
+ size.times do
20
+ buffer.push rand
21
+ end
22
+
23
+ buffer.full?.should be_true
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#push_ary' do
29
+ it 'should add the given array' do
30
+ elements = [1,2,3,4,5,6]
31
+ buffer = SPCore::CircularBuffer.new elements.count
32
+ buffer.push_ary elements
33
+ buffer.to_ary.should eq(elements)
34
+ end
35
+ end
36
+
37
+ describe '#pop' do
38
+ it 'should, with fifo set to true, after a series of pushes, report the first element pushed' do
39
+ elements = [1,2,3,4,5]
40
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => true
41
+ elements.each do |element|
42
+ buffer.push element
43
+ end
44
+ buffer.pop.should eq(elements.first)
45
+ end
46
+
47
+ it 'should, with fifo set to false, after a series of pushes, report the last element pushed' do
48
+ elements = [1,2,3,4,5]
49
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => false
50
+ elements.each do |element|
51
+ buffer.push element
52
+ end
53
+ buffer.pop.should eq(elements.last)
54
+ end
55
+ end
56
+
57
+ describe '#newest' do
58
+ it 'should, after a series of pushes, report the last element pushed' do
59
+ elements = [1,2,3,4,5]
60
+ buffer = SPCore::CircularBuffer.new elements.count
61
+ elements.each do |element|
62
+ buffer.push element
63
+ end
64
+ buffer.newest.should eq(elements.last)
65
+ end
66
+
67
+ it 'should, with fifo set to true, after a series of pushes and then a pop, report the last element pushed' do
68
+ elements = [1,2,3,4,5]
69
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => true
70
+ elements.each do |element|
71
+ buffer.push element
72
+ end
73
+ buffer.pop
74
+ buffer.newest.should eq(elements.last)
75
+ end
76
+
77
+ it 'should, with fifo set to false, after a series of pushes and then a pop, report the second to last element pushed' do
78
+ elements = [1,2,3,4,5]
79
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => false
80
+ elements.each do |element|
81
+ buffer.push element
82
+ end
83
+ buffer.pop
84
+ buffer.newest.should eq(elements[-2])
85
+ end
86
+
87
+ it 'should, when given a relative index, report the element reverse-indexed from the newest' do
88
+ elements = [1,2,3,4,5]
89
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => false
90
+ elements.each do |element|
91
+ buffer.push element
92
+ end
93
+
94
+ for i in 0...elements.count do
95
+ buffer.newest(i).should eq(elements[elements.count - 1 - i])
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#oldest' do
101
+ it 'should, after a series of pushes, report the first element pushed' do
102
+ elements = [1,2,3,4,5]
103
+ buffer = SPCore::CircularBuffer.new elements.count
104
+ elements.each do |element|
105
+ buffer.push element
106
+ end
107
+ buffer.oldest.should eq(elements.first)
108
+ end
109
+
110
+ it 'should, with fifo set to true, after a series of pushes and then a pop, report the second element pushed' do
111
+ elements = [1,2,3,4,5]
112
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => true
113
+ elements.each do |element|
114
+ buffer.push element
115
+ end
116
+ buffer.pop
117
+ buffer.oldest.should eq(elements[1])
118
+ end
119
+
120
+ it 'should, with fifo set to false, after a series of pushes and then a pop, report the first element pushed' do
121
+ elements = [1,2,3,4,5]
122
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => false
123
+ elements.each do |element|
124
+ buffer.push element
125
+ end
126
+ buffer.pop
127
+ buffer.oldest.should eq(elements.first)
128
+ end
129
+
130
+ it 'should, when given a relative index, report the element reverse-indexed from the newest' do
131
+ elements = [1,2,3,4,5]
132
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => false
133
+ elements.each do |element|
134
+ buffer.push element
135
+ end
136
+
137
+ for i in 0...elements.count do
138
+ buffer.oldest(i).should eq(elements[i])
139
+ end
140
+ end
141
+ end
142
+
143
+ describe '#to_ary' do
144
+ it 'should produce an empty array for an empty buffer' do
145
+ buffer = SPCore::CircularBuffer.new 10
146
+ buffer.to_ary.should be_empty
147
+ end
148
+
149
+ it 'should, after pushing an array of elements, produce an array of the same elements' do
150
+ elements = [1,2,3,4,5]
151
+ buffer = SPCore::CircularBuffer.new elements.count, :fifo => true
152
+ elements.each do |element|
153
+ buffer.push element
154
+ end
155
+ buffer.to_ary.should eq(elements)
156
+ end
157
+
158
+ it 'should, after pushing and popping an array of elements several times and then pushing the array one last time, produce an array of the same elements' do
159
+ elements = [1,2,3,4,5]
160
+ buffer = SPCore::CircularBuffer.new(3 * elements.count, :fifo => true)
161
+
162
+ 5.times do
163
+ elements.each do |element|
164
+ buffer.push element
165
+ end
166
+ elements.count.times do
167
+ buffer.pop
168
+ end
169
+ end
170
+
171
+ elements.each do |element|
172
+ buffer.push element
173
+ end
174
+ buffer.to_ary.should eq(elements)
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'gnuplot'
3
+ require 'pry'
4
+
5
+ describe 'cookbook filter' do
6
+ #it 'should produce a nice frequency response graph' do
7
+ # sample_rate = 44000.0
8
+ # crit_freq = 1000.0
9
+ # max_test_freq = 10000.0
10
+ # bw = 2
11
+ # filter = SPCore::CookbookNotchFilter.new sample_rate
12
+ # filter.set_critical_freq_and_bw crit_freq, bw
13
+ #
14
+ # freqs = []
15
+ # dbs = []
16
+ #
17
+ # start_freq = 10.0
18
+ # test_freq = start_freq
19
+ #
20
+ # 200.times do
21
+ # mag = filter.get_freq_magnitude_response test_freq
22
+ #
23
+ # dbs << SPCore::Gain.linear_to_db(mag)
24
+ # freqs << test_freq
25
+ #
26
+ # test_freq *= 1.035
27
+ # end
28
+ #
29
+ # Gnuplot.open do |gp|
30
+ # Gnuplot::Plot.new(gp) do |plot|
31
+ # plot.title "Frequency Magnitude Response for Lowpass Filter with Critical Freq of #{crit_freq} and BW of #{bw}"
32
+ # plot.xlabel "Frequency (f)"
33
+ # plot.ylabel "Frequency Magnitude Response (dB) at f"
34
+ # plot.logscale 'x'
35
+ #
36
+ # plot.data << Gnuplot::DataSet.new( [freqs, dbs] ) do |ds|
37
+ # ds.with = "linespoints"
38
+ # #ds.linewidth = 4
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ #end
44
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SPCore::DelayLine do
4
+ it 'should' do
5
+ SAMPLE_RATE = 44100.0
6
+ MAX_DELAY_SEC = 0.1
7
+
8
+ 5.times do
9
+ delay_line = SPCore::DelayLine.new(
10
+ :sample_rate => SAMPLE_RATE,
11
+ :max_delay_seconds => MAX_DELAY_SEC,
12
+ :delay_seconds => (rand * MAX_DELAY_SEC)
13
+ )
14
+
15
+ rand_sample = rand
16
+ delay_line.push_sample rand_sample
17
+ delay_line.delay_samples.times do
18
+ delay_line.push_sample 0.0
19
+ end
20
+
21
+ delay_line.delayed_sample.should eq(rand_sample)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SPCore::EnvelopeDetector do
4
+ describe '#process_sample' do
5
+ it 'should produce an output that follows the amplitude of the input' do
6
+ sample_rate = 10000.0
7
+ freqs = [20.0, 200.0, 2000.0]
8
+
9
+ envelope_start = 1.0
10
+ envelope_end = 0.0
11
+
12
+ freqs.each do |freq|
13
+ osc = SPCore::Oscillator.new :sample_rate => sample_rate, :frequency => freq, :amplitude => envelope_start
14
+ detector = SPCore::EnvelopeDetector.new :sample_rate => sample_rate, :attack_time => (1e-2 / freq), :release_time => (1.0 / freq)
15
+
16
+ # 1 full period to acclimate the detector to the starting envelope
17
+ (sample_rate / freq).to_i .times do
18
+ detector.process_sample osc.sample
19
+ end
20
+
21
+ input = []
22
+ output = []
23
+ envelope = []
24
+
25
+ # 5 full periods to track the envelope as it changes
26
+ sample_count = (5.0 * sample_rate / freq).to_i
27
+ sample_count.times do |n|
28
+ percent = n.to_f / sample_count
29
+ amplitude = SPCore::Interpolation.linear 0.0, envelope_start, 1.0, envelope_end, percent
30
+ osc.amplitude = amplitude
31
+
32
+ sample = osc.sample
33
+ env = detector.process_sample sample
34
+
35
+ input << n
36
+ output << sample
37
+ envelope << env
38
+
39
+ env.should be_within(0.25).of(amplitude)
40
+ end
41
+
42
+ ## plot the data
43
+ #
44
+ #Gnuplot.open do |gp|
45
+ # Gnuplot::Plot.new( gp ) do |plot|
46
+ #
47
+ # plot.title "Signal and Envelope"
48
+ # plot.ylabel "sample n"
49
+ # plot.xlabel "y[n]"
50
+ #
51
+ # plot.data = [
52
+ # Gnuplot::DataSet.new( [input, output] ) { |ds|
53
+ # ds.with = "lines"
54
+ # ds.title = "Signal"
55
+ # ds.linewidth = 2
56
+ # },
57
+ #
58
+ # Gnuplot::DataSet.new( [ input, envelope ] ) { |ds|
59
+ # ds.with = "lines"
60
+ # ds.title = "Envelope"
61
+ # ds.linewidth = 2
62
+ # }
63
+ # ]
64
+ #
65
+ # end
66
+ #end
67
+
68
+ end
69
+ end
70
+ end
71
+ end